cloudflare/cloudflared · error
failed to verify metadata JWT signature
Error message
failed to verify metadata JWT signature
What it means
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.
Source
Thrown at token/jwks.go:69
payload := jws.UnsafePayloadWithoutVerification()
var claims metadataClaims
if err := json.Unmarshal(payload, &claims); err != nil {
return nil, errors.Wrap(err, "failed to decode metadata JWT claims")
}
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) {View on GitHub (pinned to 2253eeeb25)
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.
Example fix
// typical retry driver: stale cached keys
keySet, cachedAt, err := getJWKSWithCache(authDomain)
if err != nil { return nil, err }
claims, err := verifyMetadataJWT(rawJWT, keySet)
if err != nil && !cachedAt.IsZero() && time.Since(cachedAt) >= time.Minute {
// force fresh JWKS (key rotation) and retry once
fresh, ferr := fetchJWKS(authDomain)
if ferr == nil {
claims, err = verifyMetadataJWT(rawJWT, fresh)
}
} Defensive patterns
Strategy: retry
Try / catch
claims, err := verifyMetadataWithRetry(rawJWT, authDomain)
if err != nil {
if strings.Contains(err.Error(), "failed to verify metadata JWT signature") {
// possible key rotation; clear the JWKS cache and retry once
_ = os.Remove(filepath.Join(configDir, authDomain.Hostname()+"-jwks"))
claims, err = verifyMetadataWithRetry(rawJWT, authDomain)
}
if err != nil {
return err
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid token
- metadata JWT aud is empty
- metadata JWT iat is missing or invalid
- failed to parse auth_domain %q: %w
- auth_domain %q does not end with %q
AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06).
Data as JSON: /api/errors/8a078036ddc70263.
Report an issue: GitHub.