cloudflare/cloudflared · error

metadata JWT iat is missing or invalid

Error message

metadata JWT iat is missing or invalid

What it means

validateMetadataIssuedAt checks the iat (issued-at) claim of the metadata JWT. The claim must be a positive Unix timestamp; otherwise the token's age and expiry cannot be evaluated and the function rejects it with this error. Tokens older than metadataMaxAge or too far in the future get separate errors.

Source

Thrown at token/token.go:509

	req.Header.Set(accessMetadataReqHeader, accessMetadataReqValue)
	req.Header.Set(userAgentHeader, userAgent)

	resp, err := client.Do(req) // nolint: gosec
	if err != nil {
		return "", errors.Wrap(err, "failed to get app info")
	}
	_ = resp.Body.Close()

	rawJWT := resp.Header.Get(accessMetadataRespHeader)
	if rawJWT == "" {
		return "", fmt.Errorf("failed to find Access application at %s", reqURL)
	}
	return rawJWT, nil
}

func validateMetadataIssuedAt(iat int64, now time.Time) error {
	if iat <= 0 {
		return errors.New("metadata JWT iat is missing or invalid")
	}
	issuedAt := time.Unix(iat, 0)
	if issuedAt.Before(now.Add(-metadataMaxAge)) {
		return fmt.Errorf("metadata JWT is older than %s", metadataMaxAge)
	}
	if issuedAt.After(now.Add(metadataAllowedClockSkew)) {
		return fmt.Errorf("metadata JWT is more than %s in the future", metadataAllowedClockSkew)
	}
	return nil
}

func handleRedirects(req *http.Request, via []*http.Request, orgToken string) error {
	// attach org token to login request
	if strings.Contains(req.URL.Path, AccessLoginWorkerPath) {
		req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken}) //nolint: gosec
	}

	// attach app session cookie to authorized request

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Obtain a fresh metadata JWT through the normal Access login flow so the issuer sets a valid iat.
  2. Fix the token issuer to set iat to a current Unix timestamp (seconds) when minting.
  3. Decode the token payload and confirm iat is present and positive before debugging further.
  4. Verify client clock correctness separately — a valid-looking iat far off from now will fail the adjacent age/skew checks.

Example fix

// before (issuer side): claims without IAT
 claims := metadataClaims{Aud: aud, Type: "arb"}
// after: set issued-at at mint time
 claims := metadataClaims{Aud: aud, Type: "arb", IAT: time.Now().Unix()}
Defensive patterns

Strategy: validation

Validate before calling

claims, err := decodeUnverifiedClaims(metadataJWT)
if err != nil || claims.IAT <= 0 {
    return fmt.Errorf("metadata JWT missing iat; obtain a fresh token via Access login")
}

Type guard

func hasValidIat(claims *metadataClaims, now time.Time) bool {
    return claims != nil && claims.IAT > 0 &&
        !time.Unix(claims.IAT, 0).Before(now.Add(-metadataMaxAge))
}

Try / catch

appInfo, err := GetAppInfo(ctx, req)
if err != nil {
    if strings.Contains(err.Error(), "iat is missing or invalid") {
        return refreshMetadataTokenAndRetry(ctx, req)
    }
    return fmt.Errorf("validating metadata JWT: %w", err)
}

Prevention

When it happens

Trigger: GetAppInfo validating a metadata JWT whose iat is 0, negative, or absent (missing claims decode to zero), e.g. a token minted without an issued-at field or a payload that failed to populate the timestamp.

Common situations: Hand-crafted or test tokens lacking iat, tokens issued by non-standard tooling, clock/claim corruption from a custom issuer, or a cached/stale token with a zeroed payload after partial deserialization.

Related errors


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