hashicorp/nomad · error

exchanged token is not valid; potentially expired or empty

Error message

exchanged token is not valid; potentially expired or empty

What it means

After exchanging the OIDC state and authorization code with the provider via oidcProvider.Exchange, Nomad validates the returned token with oidcToken.Valid(). If the token is invalid — expired, empty, or missing required fields — this error is returned and login fails.

Source

Thrown at nomad/acl_endpoint.go:2803

	oidcReq := a.oidcRequestCache.LoadAndDelete(args.ClientNonce) // I am so done with this NONCENSE
	if oidcReq == nil {
		// note: this may happen if there is a leader election between getting
		// the auth url and completing the login flow here.
		return errors.New("no OIDC request found for client nonce")
	}

	// Generate a context with a deadline. This is passed to the OIDC provider
	// and used when making remote HTTP requests.
	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(aclOIDCCallbackRequestExpiryTime))
	defer cancel()

	// Exchange the state and code for an OIDC provider token.
	oidcToken, err := oidcProvider.Exchange(ctx, oidcReq, args.State, args.Code)
	if err != nil {
		return fmt.Errorf("failed to exchange token with provider: %v", err)
	}
	if !oidcToken.Valid() {
		return errors.New("exchanged token is not valid; potentially expired or empty")
	}

	var idTokenClaims map[string]any
	if err := oidcToken.IDToken().Claims(&idTokenClaims); err != nil {
		return fmt.Errorf("failed to retrieve the ID token claims: %v", err)
	}

	var userClaims map[string]any
	if !authMethod.Config.OIDCDisableUserInfo {
		if userTokenSource := oidcToken.StaticTokenSource(); userTokenSource != nil {
			if err := oidcProvider.UserInfo(ctx, userTokenSource, idTokenClaims["sub"].(string), &userClaims); err != nil {
				return fmt.Errorf("failed to retrieve the user info claims: %v", err)
			}
		}
	}

	// Generate the data used by the go-bexpr selector that is an internal
	// representation of the claims that can be understood by Nomad.

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Retry the login promptly: complete the OIDC callback without long delays between authentication and OIDCCompleteAuth.
  2. Verify the OIDC provider is configured correctly and issuing valid ID tokens (test with the provider's own debugging/token endpoint).
  3. Synchronize clocks on Nomad servers and the provider (NTP) to eliminate skew-based invalidity.
  4. Check provider logs for errors at exchange time; fix client secret/redirect URIs if exchange returns degraded tokens.
Defensive patterns

Strategy: retry

Validate before calling

if code == "" || state == "" {
    return errors.New("missing state/code from provider callback; restart login")
}

Try / catch

_, err := acl.OIDCCompleteAuth(ctx, args)
if err != nil && strings.Contains(err.Error(), "exchanged token is not valid") {
    // token expired/invalid: restart the OIDC login from scratch promptly
}

Prevention

When it happens

Trigger: OIDCCompleteAuth where the provider returns an empty/expired token, the provider clock is skewed relative to the Nomad server, or the auth code was delayed so long the resulting token expired before validation.

Common situations: User sitting on the provider's login page long enough for the code/token to expire; misconfigured provider issuing tokens with no expiry or empty ID token; clock skew on the Nomad server making a fresh token look expired; provider outage returning malformed token responses.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/8fc4e41435288ba2. Report an issue: GitHub.