{"record":{"id":"41b9f24203287769","repo":"knadh/listmonk","slug":"error-getting-user-from-oidc","errorCode":null,"errorMessage":"error getting user from OIDC","messagePattern":"error getting user from OIDC","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/auth.go","lineNumber":260,"sourceCode":"\t}\n\n\tverifier, err := o.getVerifier()\n\tif err != nil {\n\t\treturn \"\", OIDCclaim{}, echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf(\"error getting verifier: %v\", err))\n\t}\n\n\tidTk, err := verifier.Verify(context.TODO(), rawIDTk)\n\tif err != nil {\n\t\treturn \"\", OIDCclaim{}, echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf(\"error verifying ID token: %v\", err))\n\t}\n\n\tif idTk.Nonce != nonce {\n\t\treturn \"\", OIDCclaim{}, echo.NewHTTPError(http.StatusUnauthorized, \"nonce did not match\")\n\t}\n\n\tvar claims OIDCclaim\n\tif err := idTk.Claims(&claims); err != nil {\n\t\treturn \"\", OIDCclaim{}, errors.New(\"error getting user from OIDC\")\n\t}\n\n\t// If claims doesn't have the e-mail, attempt to fetch it from the userinfo endpoint.\n\tif claims.Email == \"\" {\n\t\tprovider, err := o.getProvider()\n\t\tif err != nil {\n\t\t\treturn \"\", OIDCclaim{}, fmt.Errorf(\"error getting provider: %v\", err)\n\t\t}\n\n\t\tuserInfo, err := provider.UserInfo(context.TODO(), oauth2.StaticTokenSource(tk))\n\t\tif err != nil {\n\t\t\treturn \"\", OIDCclaim{}, errors.New(\"error fetching user info from OIDC\")\n\t\t}\n\n\t\t// Parse the UserInfo claims into the claims struct\n\t\tif err := userInfo.Claims(&claims); err != nil {\n\t\t\treturn \"\", OIDCclaim{}, errors.New(\"error parsing user info claims\")\n\t\t}","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/auth/auth.go#L242-L278","documentation":"This error is returned by ExchangeOIDCToken when the verified OIDC ID token's claims cannot be unmarshalled into the internal OIDCclaim struct via idTk.Claims(&claims). It means the token verified cryptographically but its JSON payload does not fit the expected claim shape, so the user identity cannot be extracted. The library discards the underlying cause, so the real reason (unexpected types, missing fields) is hidden.","triggerScenarios":"The ID token's claims JSON contains fields whose types do not match OIDCclaim (e.g. email as a non-string, numeric claims as floats vs ints) or is malformed, causing the go-oidc Claims() json.Unmarshal into *OIDCclaim to fail after successful token verification and nonce check.","commonSituations":"An identity provider emits non-standard claim types (e.g. a claim that is an object or number where OIDCclaim expects a string); a provider update changes claim shapes; a misconfigured custom claim mapping produces type mismatches.","solutions":["Check the raw ID token payload (decode at jwt.io or idToken.Claims into map[string]interface{}) and compare its types against the OIDCclaim struct field types.","Fix the OIDCclaim struct (or add custom json tags / use json.RawMessage for flexible fields) to match what your IdP actually sends.","Wrap the error with %w or log the underlying err before returning so the real mismatch is diagnosable.","Verify the expected OIDC scopes are requested so standard claims (email, name) are present and well-typed."],"exampleFix":"// before\nvar claims OIDCclaim\nif err := idTk.Claims(&claims); err != nil {\n\treturn \"\", OIDCclaim{}, errors.New(\"error getting user from OIDC\")\n}\n// after\nvar claims OIDCclaim\nif err := idTk.Claims(&claims); err != nil {\n\treturn \"\", OIDCclaim{}, fmt.Errorf(\"error getting user from OIDC: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"var probe map[string]interface{}\nif err := json.Unmarshal(rawIDTokenPayload, &probe); err != nil {\n\t// token claims are not valid JSON; do not call ExchangeOIDCToken flow\n}","typeGuard":"func hasWellTypedClaims(m map[string]interface{}) bool {\n\tfor _, k := range []string{\"email\", \"name\", \"sub\"} {\n\t\tif v, ok := m[k]; ok {\n\t\t\tif _, ok := v.(string); !ok {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"if _, claims, err := auth.ExchangeOIDCToken(code, nonce); err != nil {\n\tif strings.Contains(err.Error(), \"error getting user from OIDC\") {\n\t\tlog.Printf(\"OIDC claim parse failure: %v\", err) // 500, not retryable\n\t}\n\treturn err\n}","preventionTips":["Keep OIDCclaim field types aligned with your IdP's claim types; test with a real token from the IdP.","Request standard scopes (openid, email, profile) so claims are well-formed.","Decode a sample ID token payload and diff it against the struct when upgrading IdPs.","Preserve the wrapped error (%w) in custom builds to aid debugging."],"tags":["oidc","authentication","claims-parsing"],"backgroundTag":"oidc-claims-parse-failed","analyzedSha":"670c01717d48647093335cc23a6be6f4b79c3b6b","analyzedAt":"2026-09-01T03:39:35.452Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}