{"record":{"id":"8c095b245ce4cfb7","repo":"hashicorp/nomad","slug":"failed-to-retrieve-the-user-info-claims-v","errorCode":null,"errorMessage":"failed to retrieve the user info claims: %v","messagePattern":"failed to retrieve the user info claims: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/acl_endpoint.go","lineNumber":2815,"sourceCode":"\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}\n\n\t// Generate the data used by the go-bexpr selector that is an internal\n\t// representation of the claims that can be understood by Nomad.\n\toidcInternalClaims, err := auth.SelectorData(authMethod, idTokenClaims, userClaims)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// No need to do all this marshaling if VerboseLogging is disabled\n\tif authMethod.Config.VerboseLogging {\n\t\tidTokenClaimBytes, err := json.MarshalIndent(idTokenClaims, \"\", \" \")\n\t\tif err != nil {\n\t\t\tvlog.Debug(\"failed to marshal ID token claims\")\n\t\t}\n","sourceCodeStart":2797,"sourceCodeEnd":2833,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/acl_endpoint.go#L2797-L2833","documentation":"Thrown when oidcProvider.UserInfo(ctx, userTokenSource, sub, &userClaims) fails while fetching the provider's userinfo endpoint using the exchanged token. The wrapped error carries the IdP's response (401/403, network, or decode failure).","triggerScenarios":"OIDCDisableUserInfo is false (default), the token has a StaticTokenSource, and UserInfo() errors: the access token is rejected by the /userinfo endpoint, the sub claim is missing/typed unexpectedly, or the endpoint is unreachable.","commonSituations":"IdP userinfo endpoint requires scopes not requested (e.g. missing 'profile'/'email' scope), token audience does not include userinfo, corporate proxy blocks the call from Nomad servers, IdP sub claim type mismatch (numeric vs string causing the idTokenClaims[\"sub\"].(string) assertion context).","solutions":["Check the wrapped cause: 401/403 usually means missing scopes or audience — update BoundAudiences/RequestedScopes in the auth method config.","If the ID token already carries all needed claims, set OIDCDisableUserInfo = true in the auth method config to skip this call entirely.","Verify the IdP's userinfo endpoint is reachable from Nomad servers (network, proxy, DNS).","Confirm the 'sub' claim is present in the ID token and is a string (JSON number sub values break UserInfo lookups)."],"exampleFix":"// before (nomad auth-method config)\n{ \"OIDCClientID\": \"nomad\", \"BoundAudiences\": [\"nomad\"] }\n// after: skip userinfo if ID token has all claims\n{ \"OIDCClientID\": \"nomad\", \"BoundAudiences\": [\"nomad\"], \"OIDCDisableUserInfo\": true }","handlingStrategy":"validation","validationCode":"// ensure requested scopes cover userinfo requirements and sub exists\nrequired := map[string]bool{\"openid\": true, \"profile\": true}\nfor _, s := range method.Config.OIDCScopes { delete(required, s) }\nif len(required) > 0 { return fmt.Errorf(\"missing scopes: %v\", required) }","typeGuard":"func subIsString(idTokenClaims map[string]any) bool {\n  v, ok := idTokenClaims[\"sub\"]\n  return ok && isString(v)\n}\nfunc isString(v any) bool { _, ok := v.(string); return ok }","tryCatchPattern":"if err := oidcProvider.UserInfo(ctx, src, sub, &userClaims); err != nil {\n    return fmt.Errorf(\"failed to retrieve the user info claims: %v\", err)\n}\n// on 401/403 → add scopes/audiences; on network error → check egress from servers","preventionTips":["Set OIDCDisableUserInfo=true when the ID token already contains all mapped claims.","Request profile/email scopes explicitly in the auth method config.","Test IdP userinfo endpoint reachability from Nomad servers during setup."],"tags":["oidc","userinfo","nomad","claims"],"backgroundTag":"oidc-userinfo-request-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"}