cloudflare/cloudflared · warning
failed to decode verified metadata JWT claims
Error message
failed to decode verified metadata JWT claims
What it means
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.
Source
Thrown at token/jwks.go:74
return &claims, nil
}
// verifyMetadataJWT verifies the metadata JWT signature against the provided
// JWKS and returns the decoded claims.
func verifyMetadataJWT(rawJWT string, keySet *jose.JSONWebKeySet) (*metadataClaims, error) {
jws, err := jose.ParseSigned(rawJWT, signatureAlgs)
if err != nil {
return nil, errors.Wrap(err, "failed to parse metadata JWT")
}
payload, err := jws.Verify(keySet)
if err != nil {
return nil, errors.Wrap(err, "failed to verify metadata JWT signature")
}
var claims metadataClaims
if err := json.Unmarshal(payload, &claims); err != nil {
return nil, errors.Wrap(err, "failed to decode verified metadata JWT claims")
}
return &claims, nil
}
// parseAuthDomain extracts the canonical hostname used for JWKS requests and
// cache paths from the auth_domain claim.
func parseAuthDomain(authDomain string) (url.URL, error) {
parsed, err := url.Parse(httpsScheme + "://" + authDomain)
if err != nil {
return url.URL{}, fmt.Errorf("failed to parse auth_domain %q: %w", authDomain, err)
}
hostname := strings.ToLower(parsed.Hostname())
if !strings.HasSuffix(hostname, accessDomainSuffix) {
return url.URL{}, fmt.Errorf("auth_domain %q does not end with %q", authDomain, accessDomainSuffix)
}
return url.URL{Scheme: httpsScheme, Host: hostname}, nil
}
View on GitHub (pinned to 2253eeeb25)
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.
Defensive patterns
Strategy: type-guard
Validate before calling
var _ = json.Valid // payload sanity check before trusting claims
func payloadIsValidJSON(token string) bool {
parts := strings.Split(strings.TrimSpace(token), ".")
if len(parts) < 2 { return false }
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
return err == nil && json.Valid(payload)
} Type guard
func isMetadataClaims(m map[string]any) bool {
_, hasAud := m["aud"].(string)
_, hasDomain := m["auth_domain"].(string)
return hasAud && hasDomain
} Try / catch
claims, err := verifyMetadataJWT(rawJWT, keySet)
if err != nil && strings.Contains(err.Error(), "failed to decode verified metadata JWT claims") {
return fmt.Errorf("signature valid but payload is not metadata claims — wrong token type or schema change: %w", err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode metadata JWT claims
- invalid token
- metadata JWT aud is empty
- metadata JWT iat is missing or invalid
- failed to parse auth_domain %q: %w
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/eaec722b312d7e69.
Report an issue: GitHub.