{"record":{"id":"964825c1b510674c","repo":"cloudflare/cloudflared","slug":"metadata-jwt-iat-is-missing-or-invalid","errorCode":null,"errorMessage":"metadata JWT iat is missing or invalid","messagePattern":"metadata JWT iat is missing or invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/token.go","lineNumber":509,"sourceCode":"\treq.Header.Set(accessMetadataReqHeader, accessMetadataReqValue)\n\treq.Header.Set(userAgentHeader, userAgent)\n\n\tresp, err := client.Do(req) // nolint: gosec\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to get app info\")\n\t}\n\t_ = resp.Body.Close()\n\n\trawJWT := resp.Header.Get(accessMetadataRespHeader)\n\tif rawJWT == \"\" {\n\t\treturn \"\", fmt.Errorf(\"failed to find Access application at %s\", reqURL)\n\t}\n\treturn rawJWT, nil\n}\n\nfunc validateMetadataIssuedAt(iat int64, now time.Time) error {\n\tif iat <= 0 {\n\t\treturn errors.New(\"metadata JWT iat is missing or invalid\")\n\t}\n\tissuedAt := time.Unix(iat, 0)\n\tif issuedAt.Before(now.Add(-metadataMaxAge)) {\n\t\treturn fmt.Errorf(\"metadata JWT is older than %s\", metadataMaxAge)\n\t}\n\tif issuedAt.After(now.Add(metadataAllowedClockSkew)) {\n\t\treturn fmt.Errorf(\"metadata JWT is more than %s in the future\", metadataAllowedClockSkew)\n\t}\n\treturn nil\n}\n\nfunc handleRedirects(req *http.Request, via []*http.Request, orgToken string) error {\n\t// attach org token to login request\n\tif strings.Contains(req.URL.Path, AccessLoginWorkerPath) {\n\t\treq.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken}) //nolint: gosec\n\t}\n\n\t// attach app session cookie to authorized request","sourceCodeStart":491,"sourceCodeEnd":527,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/token.go#L491-L527","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (issuer side): claims without IAT\n claims := metadataClaims{Aud: aud, Type: \"arb\"}\n// after: set issued-at at mint time\n claims := metadataClaims{Aud: aud, Type: \"arb\", IAT: time.Now().Unix()}","handlingStrategy":"validation","validationCode":"claims, err := decodeUnverifiedClaims(metadataJWT)\nif err != nil || claims.IAT <= 0 {\n    return fmt.Errorf(\"metadata JWT missing iat; obtain a fresh token via Access login\")\n}","typeGuard":"func hasValidIat(claims *metadataClaims, now time.Time) bool {\n    return claims != nil && claims.IAT > 0 &&\n        !time.Unix(claims.IAT, 0).Before(now.Add(-metadataMaxAge))\n}","tryCatchPattern":"appInfo, err := GetAppInfo(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"iat is missing or invalid\") {\n        return refreshMetadataTokenAndRetry(ctx, req)\n    }\n    return fmt.Errorf(\"validating metadata JWT: %w\", err)\n}","preventionTips":["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."],"tags":["jwt","token-validation","issued-at","cloudflare-access"],"backgroundTag":"invalid-date-format","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}