cloudflare/cloudflared · error

failed to parse metadata JWT

Error message

failed to parse metadata JWT

What it means

decodeMetadataUnverified parses an Access metadata JWT with go-jose's ParseSigned before reading its payload without signature verification. This error means the raw JWT is not a well-formed compact JWS with an allowed signature algorithm (only RS256 is accepted here). It is returned by GetAppInfo when handed a malformed token string.

Source

Thrown at token/jwks.go:48

// metadataClaims represents the claims in the signed metadata JWT returned
// by the Cloudflare Access edge when CF-Access-Metadata-Request: true is set.
type metadataClaims struct {
	Type       string `json:"type"`
	Hostname   string `json:"hostname"`
	AuthDomain string `json:"auth_domain"`
	AUD        string `json:"aud"`
	// This is the hostname as defined in the Access application, including wildcards.
	AppHostname string `json:"app_hostname"`
	IAT         int64  `json:"iat"`
}

// decodeMetadataUnverified decodes the JWT payload without verifying the
// signature.
func decodeMetadataUnverified(rawJWT string) (*metadataClaims, error) {
	jws, err := jose.ParseSigned(rawJWT, signatureAlgs)
	if err != nil {
		return nil, errors.Wrap(err, "failed to parse metadata JWT")
	}

	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")
	}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Pass the raw CF_Authorization JWT exactly as received (URL-encode it when placing in a query string, never pre-decode).
  2. Verify the token has the compact JWS form header.payload.signature with three dot-separated segments.
  3. Confirm the token is the Access metadata JWT and not a session cookie or opaque token.
  4. Log the first few characters of the failing value (never the whole token) to spot truncation or whitespace/newlines.

Example fix

// before: token may carry surrounding whitespace
token := r.Header.Get("Cf-Access-Jwt-Assertion")
claims, err := GetAppInfo(token)
// after
token := strings.TrimSpace(r.Header.Get("Cf-Access-Jwt-Assertion"))
if strings.Count(token, ".") != 2 {
    return errors.New("malformed Access JWT")
}
claims, err := GetAppInfo(token)
Defensive patterns

Strategy: validation

Validate before calling

func looksLikeCompactJWS(token string) bool {
    token = strings.TrimSpace(token)
    parts := strings.Split(token, ".")
    return len(parts) == 3 && len(parts[0]) > 0 && len(parts[1]) > 0
}

Try / catch

claims, err := GetAppInfo(rawJWT)
if err != nil && strings.Contains(err.Error(), "failed to parse metadata JWT") {
    return fmt.Errorf("token is not a well-formed Access metadata JWT (check truncation/encoding): %w", err)
}

Prevention

When it happens

Trigger: Calling GetAppInfo (-> decodeMetadataUnverified) with a rawJWT that is empty, truncated, not base64url-encoded in three dot-separated parts, uses a disallowed algorithm header (e.g. ES256), or is a plain (unsigned) JWS token.

Common situations: Passing a CF_Authorization cookie value that was URL-decoded or truncated; grabbing the wrong header/cookie (e.g. a session id instead of the Access JWT); a token missing its signature segment; algorithm mismatch after an edge-side key rotation to a non-RS256 alg.

Understand the failure class

Related errors


AI-assisted analysis of cloudflare/cloudflared@2253eeeb25 (2026-09-06). Data as JSON: /api/errors/cb5f90035dfc3351. Report an issue: GitHub.