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
- Retry the login promptly: complete the OIDC callback without long delays between authentication and OIDCCompleteAuth.
- Verify the OIDC provider is configured correctly and issuing valid ID tokens (test with the provider's own debugging/token endpoint).
- Synchronize clocks on Nomad servers and the provider (NTP) to eliminate skew-based invalidity.
- 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
- Finish the login flow quickly; don't let the auth code sit unused.
- Keep server clocks NTP-synchronized.
- Validate the OIDC provider configuration in a staging environment before production.
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
- no OIDC request found for client nonce
- token name too long
- client token missing policies or roles
- management token cannot be associated with policies or roles
- token type must be client or management
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/8fc4e41435288ba2.
Report an issue: GitHub.