{"record":{"id":"8a078036ddc70263","repo":"cloudflare/cloudflared","slug":"failed-to-verify-metadata-jwt-signature","errorCode":null,"errorMessage":"failed to verify metadata JWT signature","messagePattern":"failed to verify metadata JWT signature","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"token/jwks.go","lineNumber":69,"sourceCode":"\tpayload := jws.UnsafePayloadWithoutVerification()\n\tvar claims metadataClaims\n\tif err := json.Unmarshal(payload, &claims); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to decode metadata JWT claims\")\n\t}\n\treturn &claims, nil\n}\n\n// verifyMetadataJWT verifies the metadata JWT signature against the provided\n// JWKS and returns the decoded claims.\nfunc verifyMetadataJWT(rawJWT string, keySet *jose.JSONWebKeySet) (*metadataClaims, error) {\n\tjws, err := jose.ParseSigned(rawJWT, signatureAlgs)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to parse metadata JWT\")\n\t}\n\n\tpayload, err := jws.Verify(keySet)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to verify metadata JWT signature\")\n\t}\n\n\tvar claims metadataClaims\n\tif err := json.Unmarshal(payload, &claims); err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to decode verified metadata JWT claims\")\n\t}\n\treturn &claims, nil\n}\n\n// parseAuthDomain extracts the canonical hostname used for JWKS requests and\n// cache paths from the auth_domain claim.\nfunc parseAuthDomain(authDomain string) (url.URL, error) {\n\tparsed, err := url.Parse(httpsScheme + \"://\" + authDomain)\n\tif err != nil {\n\t\treturn url.URL{}, fmt.Errorf(\"failed to parse auth_domain %q: %w\", authDomain, err)\n\t}\n\thostname := strings.ToLower(parsed.Hostname())\n\tif !strings.HasSuffix(hostname, accessDomainSuffix) {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/jwks.go#L51-L87","documentation":"After successful parsing, verifyMetadataJWT calls jws.Verify(keySet) to check the RS256 signature against the JWKS keys for the auth domain. This error means the signature does not validate with any key in the provided set — wrong key, expired/rotated key set, kid mismatch, or an altered payload. verifyMetadataWithRetry retries once with a refreshed JWKS when the cached keys are old enough.","triggerScenarios":"verifyMetadataJWT/verifyMetadataWithRetry where jws.Verify(keySet) fails: the JWKS has no key matching the token's kid, the token was signed by a different Access team/domain, the token payload was modified, or the cache holds pre-rotation keys within the minimum refresh interval.","commonSituations":"Cloudflare rotated signing keys but the local 24h JWKS cache still has the old keys; validating a token from team A against team B's auth_domain; someone edited the JWT payload; clock/token reuse across environments.","solutions":["Wait for or force a JWKS cache refresh (retry after jwksMinRefreshInterval) so rotated keys are picked up; or delete the *-jwks cache file in the cloudflared config directory.","Validate the token against the auth_domain claim's JWKS, not a different team's endpoint.","Re-obtain the token — an invalid signature on an untouched token usually means it is not meant for this verifier.","Decode header/payload offline and confirm the kid exists in the fetched JWKS before deeper debugging."],"exampleFix":"// typical retry driver: stale cached keys\nkeySet, cachedAt, err := getJWKSWithCache(authDomain)\nif err != nil { return nil, err }\nclaims, err := verifyMetadataJWT(rawJWT, keySet)\nif err != nil && !cachedAt.IsZero() && time.Since(cachedAt) >= time.Minute {\n    // force fresh JWKS (key rotation) and retry once\n    fresh, ferr := fetchJWKS(authDomain)\n    if ferr == nil {\n        claims, err = verifyMetadataJWT(rawJWT, fresh)\n    }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"claims, err := verifyMetadataWithRetry(rawJWT, authDomain)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to verify metadata JWT signature\") {\n        // possible key rotation; clear the JWKS cache and retry once\n        _ = os.Remove(filepath.Join(configDir, authDomain.Hostname()+\"-jwks\"))\n        claims, err = verifyMetadataWithRetry(rawJWT, authDomain)\n    }\n    if err != nil {\n        return err\n    }\n}","preventionTips":["Always validate against the JWKS of the token's own auth_domain claim.","Allow the built-in refresh retry to work — do not cache validation failures too aggressively.","Know where the JWKS cache lives and clear it after Cloudflare key rotation incidents.","Never modify a token's payload in transit."],"tags":["jwt","signature","jwks","key-rotation","cloudflare-access"],"backgroundTag":"jwt-signature-verification-failed","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}