cloudflare/cloudflared · error
metadata JWT iat is missing or invalid
Error message
metadata JWT iat is missing or invalid
What it means
validateMetadataIssuedAt checks the iat (issued-at) claim of the metadata JWT. The claim must be a positive Unix timestamp; otherwise the token's age and expiry cannot be evaluated and the function rejects it with this error. Tokens older than metadataMaxAge or too far in the future get separate errors.
Source
Thrown at token/token.go:509
req.Header.Set(accessMetadataReqHeader, accessMetadataReqValue)
req.Header.Set(userAgentHeader, userAgent)
resp, err := client.Do(req) // nolint: gosec
if err != nil {
return "", errors.Wrap(err, "failed to get app info")
}
_ = resp.Body.Close()
rawJWT := resp.Header.Get(accessMetadataRespHeader)
if rawJWT == "" {
return "", fmt.Errorf("failed to find Access application at %s", reqURL)
}
return rawJWT, nil
}
func validateMetadataIssuedAt(iat int64, now time.Time) error {
if iat <= 0 {
return errors.New("metadata JWT iat is missing or invalid")
}
issuedAt := time.Unix(iat, 0)
if issuedAt.Before(now.Add(-metadataMaxAge)) {
return fmt.Errorf("metadata JWT is older than %s", metadataMaxAge)
}
if issuedAt.After(now.Add(metadataAllowedClockSkew)) {
return fmt.Errorf("metadata JWT is more than %s in the future", metadataAllowedClockSkew)
}
return nil
}
func handleRedirects(req *http.Request, via []*http.Request, orgToken string) error {
// attach org token to login request
if strings.Contains(req.URL.Path, AccessLoginWorkerPath) {
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken}) //nolint: gosec
}
// attach app session cookie to authorized requestView on GitHub (pinned to 2253eeeb25)
Solutions
- Obtain a fresh metadata JWT through the normal Access login flow so the issuer sets a valid iat.
- Fix the token issuer to set iat to a current Unix timestamp (seconds) when minting.
- Decode the token payload and confirm iat is present and positive before debugging further.
- Verify client clock correctness separately — a valid-looking iat far off from now will fail the adjacent age/skew checks.
Example fix
// before (issuer side): claims without IAT
claims := metadataClaims{Aud: aud, Type: "arb"}
// after: set issued-at at mint time
claims := metadataClaims{Aud: aud, Type: "arb", IAT: time.Now().Unix()} Defensive patterns
Strategy: validation
Validate before calling
claims, err := decodeUnverifiedClaims(metadataJWT)
if err != nil || claims.IAT <= 0 {
return fmt.Errorf("metadata JWT missing iat; obtain a fresh token via Access login")
} Type guard
func hasValidIat(claims *metadataClaims, now time.Time) bool {
return claims != nil && claims.IAT > 0 &&
!time.Unix(claims.IAT, 0).Before(now.Add(-metadataMaxAge))
} Try / catch
appInfo, err := GetAppInfo(ctx, req)
if err != nil {
if strings.Contains(err.Error(), "iat is missing or invalid") {
return refreshMetadataTokenAndRetry(ctx, req)
}
return fmt.Errorf("validating metadata JWT: %w", err)
} Prevention
- Mint tokens only through the standard flow so iat is always set to a current Unix timestamp.
- Check local clock sync (NTP) so fresh tokens are not rejected by age/skew checks.
- Decode token payloads in CI tests to assert iat presence before integration runs.
- Discard cached tokens whose payload is partially populated or zero-valued.
When it happens
Trigger: GetAppInfo validating a metadata JWT whose iat is 0, negative, or absent (missing claims decode to zero), e.g. a token minted without an issued-at field or a payload that failed to populate the timestamp.
Common situations: Hand-crafted or test tokens lacking iat, tokens issued by non-standard tooling, clock/claim corruption from a custom issuer, or a cached/stale token with a zeroed payload after partial deserialization.
Related errors
- invalid token
- metadata JWT aud is empty
- failed to parse auth_domain %q: %w
- auth_domain %q does not end with %q
- metadata JWT auth_domain validation failed: %w
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/964825c1b510674c.
Report an issue: GitHub.