cloudflare/cloudflared · error

token is invalid: %s

Error message

token is invalid: %s

What it means

This error is returned by Access.Validate when the JWT verifier (a.verifier.Verify) rejects the presented Access token. It wraps the underlying verification error (bad signature, expired token, wrong audience, malformed JWT) together with the offending token string. It means the token cannot be trusted and the request must be rejected.

Source

Thrown at validation/validation.go:198

	}

	issuerURL, err := validateUrlString(issuer)
	if err != nil {
		return nil, err
	}

	// An issuerURL from Cloudflare Access will always use HTTPS.
	issuerURL = strings.Replace(issuerURL, "http:", "https:", 1)

	keySet := oidc.NewRemoteKeySet(ctx, domainURL+accessCertPath)
	return &Access{oidc.NewVerifier(issuerURL, keySet, &oidc.Config{ClientID: applicationAUD})}, nil
}

func (a *Access) Validate(ctx context.Context, jwt string) error {
	token, err := a.verifier.Verify(ctx, jwt)

	if err != nil {
		return errors.Wrapf(err, "token is invalid: %s", jwt)
	}

	// Perform extra sanity checks, just to be safe.

	if token == nil {
		return fmt.Errorf("token is nil: %s", jwt)
	}

	if !strings.HasSuffix(token.Issuer, accessDomain) {
		return fmt.Errorf("token has non-cloudflare issuer of %s: %s", token.Issuer, jwt)
	}

	return nil
}

func (a *Access) ValidateRequest(ctx context.Context, r *http.Request) error {
	return a.Validate(ctx, r.Header.Get(accessJwtHeader))
}

View on GitHub (pinned to 2253eeeb25)

Solutions

  1. Have the user re-authenticate to the Access application to obtain a fresh JWT
  2. Verify the application's AUD tag configured in the validator matches the one in the token (jwt.io decode of the payload)
  3. Confirm the machine's clock is synchronized (NTP) to avoid expiry misjudgment
  4. Ensure the token header is forwarded intact (not stripped by a proxy/load balancer)
  5. Check that the team's public keys/certs used by the verifier are current

Example fix

// before: treating any validate error as fatal misconfiguration
if err := validator.Validate(ctx, jwt); err != nil {
	panic(err)
}
// after: distinguish expired token (re-auth needed) from other failures
if err := validator.Validate(ctx, jwt); err != nil {
	log.Warn().Err(err).Msg("access token rejected, requiring re-authentication")
	http.Error(w, "unauthorized: please re-authenticate via Cloudflare Access", http.StatusUnauthorized)
	return
}
Defensive patterns

Strategy: try-catch

Validate before calling

parts := strings.Split(jwt, ".")
if len(parts) != 3 {
	return errors.New("malformed JWT: expected 3 dot-separated segments")
}
// decode payload to pre-check exp and aud before Validate
payload, _ := base64.RawURLEncoding.DecodeString(parts[1])
var claims struct{ Exp int64 `json:"exp"`; Aud []string `json:"aud"` }
json.Unmarshal(payload, &claims)
if time.Now().Unix() > claims.Exp {
	return errors.New("token already expired; re-authenticate")
}

Try / catch

if err := validator.Validate(ctx, jwt); err != nil {
	var httpErr *httpError
	log.Warn().Err(err).Msg("access JWT rejected")
	http.Error(w, "unauthorized", http.StatusUnauthorized)
	return
}

Prevention

When it happens

Trigger: ValidateRequest passes a request's Cf-Access-Jwt-Assertion JWT to Access.Validate, and verifier.Verify fails: token expired, signature invalid, signed by an unexpected team cert, wrong AUD tag, or token truncated/corrupted in transit.

Common situations: User's Access session expired and the browser still sends the old JWT; application AUD changed after config edits; token copied between applications with different audiences; clock skew between validating host and Cloudflare edge; tokens forwarded incorrectly by a proxy.

Understand the failure class

Related errors


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