{"record":{"id":"4d5e48c2477c5b21","repo":"hashicorp/nomad","slug":"failed-to-exchange-token-with-provider-v","errorCode":null,"errorMessage":"failed to exchange token with provider: %v","messagePattern":"failed to exchange token with provider: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/acl_endpoint.go","lineNumber":2800,"sourceCode":"\t}\n\n\t// Retrieve the request generated in OIDCAuthURL()\n\toidcReq := a.oidcRequestCache.LoadAndDelete(args.ClientNonce) // I am so done with this NONCENSE\n\tif oidcReq == nil {\n\t\t// note: this may happen if there is a leader election between getting\n\t\t// the auth url and completing the login flow here.\n\t\treturn errors.New(\"no OIDC request found for client nonce\")\n\t}\n\n\t// Generate a context with a deadline. This is passed to the OIDC provider\n\t// and used when making remote HTTP requests.\n\tctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(aclOIDCCallbackRequestExpiryTime))\n\tdefer cancel()\n\n\t// Exchange the state and code for an OIDC provider token.\n\toidcToken, err := oidcProvider.Exchange(ctx, oidcReq, args.State, args.Code)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to exchange token with provider: %v\", err)\n\t}\n\tif !oidcToken.Valid() {\n\t\treturn errors.New(\"exchanged token is not valid; potentially expired or empty\")\n\t}\n\n\tvar idTokenClaims map[string]any\n\tif err := oidcToken.IDToken().Claims(&idTokenClaims); err != nil {\n\t\treturn fmt.Errorf(\"failed to retrieve the ID token claims: %v\", err)\n\t}\n\n\tvar userClaims map[string]any\n\tif !authMethod.Config.OIDCDisableUserInfo {\n\t\tif userTokenSource := oidcToken.StaticTokenSource(); userTokenSource != nil {\n\t\t\tif err := oidcProvider.UserInfo(ctx, userTokenSource, idTokenClaims[\"sub\"].(string), &userClaims); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to retrieve the user info claims: %v\", err)\n\t\t\t}\n\t\t}\n\t}","sourceCodeStart":2782,"sourceCodeEnd":2818,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/acl_endpoint.go#L2782-L2818","documentation":"Thrown by OIDCCompleteAuth when the cap/go-oidc provider's Exchange() call fails to swap the OIDC authorization code (plus signed state) for a provider token. The wrapped provider error is embedded via %v, so the underlying cause (network, TLS, invalid code, client auth) is in the message. This happens server-side in Nomad during the callback step of the OIDC login flow.","triggerScenarios":"ACL.oidcClient.Exchange(ctx, oidcReq, args.State, args.Code) returns an error: the auth code was already used or expired, the state does not match, the provider is unreachable, or the client id/secret (or client assertion) is wrong.","commonSituations":"User reloads or double-submits the callback URL (code replay), clock skew between Nomad and the IdP, wrong OIDCClientID/OIDCClientSecret in the auth method config, IdP behind a firewall/DNS failure from the Nomad servers, provider token endpoint rejecting private_key_jwt client assertion.","solutions":["Check the wrapped cause in the error message; fix the specific provider complaint (invalid code, invalid client, network).","Ensure the callback is hit exactly once — auth codes are single-use; restart the login flow if the browser reloaded the callback.","Verify OIDCClientID, OIDCClientSecret / OIDCClientAssertion (client-id, key-source) in the ACL auth method config match the IdP registration.","From a Nomad server, verify network/DNS/TLS reachability of the provider's discovery URL.","Check clock skew on Nomad servers and the IdP (NTP)."],"exampleFix":"// before: reusing a stale callback URL\ncurl 'https://nomad/ui/oidc/callback?code=OLD_CODE&state=OLD_STATE'\n// after: start a fresh login\n# navigate to https://nomad/ui/settings/tokens and click 'Sign in with SSO' again","handlingStrategy":"try-catch","validationCode":"// before calling Login/complete auth, verify config + reachability\nfor _, u := range []string{method.Config.DiscoveryURL} {\n  resp, err := http.Get(u + \"/.well-known/openid-configuration\")\n  if err != nil { return fmt.Errorf(\"provider unreachable: %w\", err) }\n  resp.Body.Close()\n}","typeGuard":null,"tryCatchPattern":"token, err := oidcProvider.Exchange(ctx, req, state, code)\nif err != nil {\n    return fmt.Errorf(\"failed to exchange token with provider: %v\", err)\n}\n// inspect err string for 'invalid_grant' → restart flow; 'connection refused' → check network","preventionTips":["Never reuse or reload callback URLs — auth codes are single-use; always restart the login flow.","Validate OIDCClientID/Secret against the IdP registration before saving the auth method.","Monitor NTP/clock skew on Nomad servers and the IdP.","Smoke-test provider reachability from every Nomad server after network changes."],"tags":["oidc","auth","nomad","token-exchange"],"backgroundTag":"oidc-token-exchange-failed","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}