cloudflare/cloudflared · warning

token is nil: %s

Error message

token is nil: %s

What it means

Access.Validate verifies the Cloudflare Access JWT with the OIDC verifier, then runs sanity checks. This error means Verify returned successfully but produced a nil token — a defensive invariant check that should be nearly unreachable; if it fires, the verifier/key-set setup is suspect. The full JWT is included in the message.

Source

Thrown at validation/validation.go:204

	// 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. Log the JWT and report/inspect why oidc IDTokenVerifier.Verify returned nil token with nil error
  2. Re-create the validator with correct NewAccessValidator(domain, issuer, aud) arguments and confirm key set URL (domain + /cdn-cgi/access/certs)
  3. Pin/upgrade github.com/coreos/go-oidc/v3 to a version where Verify never returns (nil, nil) for a non-empty jwt
  4. Guard callers with an explicit token!=nil check and fail closed

Example fix

// defensive caller-side guard
if jwt == "" {
    return errors.New("missing Cf-Access-Jwt-Assertion header")
}
if err := validator.Validate(ctx, jwt); err != nil {
    return fmt.Errorf("access denied: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

func hasJWT(r *http.Request) bool {
    return r.Header.Get("Cf-Access-Jwt-Assertion") != ""
}

Type guard

func nonEmptyJWT(jwt string) bool { return jwt != "" }

Try / catch

if err := validator.Validate(ctx, jwt); err != nil {
    log.Warn().Err(err).Msg("access token sanity check failed")
    http.Error(w, "Unauthorized", http.StatusUnauthorized) // fail closed
}

Prevention

When it happens

Trigger: a.verifier.Verify(ctx, jwt) returning (nil, nil) — effectively an internal invariant violation in the oidc verifier integration; reached via Access.Validate or Access.ValidateRequest (Cf-Access-Jwt-Assertion header).

Common situations: Practically only seen with a misconfigured NewAccessValidator or an unexpected oidc library version/behavior; treat it as a bug indicator rather than a user input problem.

Related errors


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