{"record":{"id":"eaec722b312d7e69","repo":"cloudflare/cloudflared","slug":"failed-to-decode-verified-metadata-jwt-claims","errorCode":null,"errorMessage":"failed to decode verified metadata JWT claims","messagePattern":"failed to decode verified metadata JWT claims","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"token/jwks.go","lineNumber":74,"sourceCode":"\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) {\n\t\treturn url.URL{}, fmt.Errorf(\"auth_domain %q does not end with %q\", authDomain, accessDomainSuffix)\n\t}\n\treturn url.URL{Scheme: httpsScheme, Host: hostname}, nil\n}\n","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/token/jwks.go#L56-L92","documentation":"Once the metadata JWT signature verifies, verifyMetadataJWT json.Unmarshal's the verified payload into metadataClaims. This error means the signature was valid but the payload is not JSON matching the claims schema — very rare, since Cloudflare always emits the documented claim set. It usually indicates a non-metadata JWT that happens to carry a valid signature from the same key set.","triggerScenarios":"verifyMetadataJWT with a signed token whose payload is empty, non-JSON, or has type-incompatible claims (e.g. aud as an array of strings instead of a string) after successful jws.Verify.","commonSituations":"Feeding a different kind of Cloudflare-signed token (e.g. an application session token) through the metadata verification path; an edge API change producing an unexpected claim schema in newer tokens.","solutions":["Confirm the token came from the metadata endpoint (CF-Access-Metadata-Request: true) and not another Access surface.","Decode the payload offline and compare claim types against metadataClaims (type, hostname, auth_domain, aud, app_hostname, iat).","Update cloudflared if Cloudflare changed the metadata claim schema — the struct may lag a new format.","File an upstream issue with the (redacted) header/claim names if a current token still fails."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"var _ = json.Valid // payload sanity check before trusting claims\nfunc payloadIsValidJSON(token string) bool {\n    parts := strings.Split(strings.TrimSpace(token), \".\")\n    if len(parts) < 2 { return false }\n    payload, err := base64.RawURLEncoding.DecodeString(parts[1])\n    return err == nil && json.Valid(payload)\n}","typeGuard":"func isMetadataClaims(m map[string]any) bool {\n    _, hasAud := m[\"aud\"].(string)\n    _, hasDomain := m[\"auth_domain\"].(string)\n    return hasAud && hasDomain\n}","tryCatchPattern":"claims, err := verifyMetadataJWT(rawJWT, keySet)\nif err != nil && strings.Contains(err.Error(), \"failed to decode verified metadata JWT claims\") {\n    return fmt.Errorf(\"signature valid but payload is not metadata claims — wrong token type or schema change: %w\", err)\n}","preventionTips":["Route only metadata-endpoint tokens through this verifier.","Keep cloudflared updated so metadataClaims matches current edge output.","Spot-check payloads offline after any Cloudflare Access product update."],"tags":["jwt","json","claims","cloudflare-access"],"backgroundTag":"json-unmarshal-failed","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"}