{"record":{"id":"0abefa890fcdb8bf","repo":"knadh/listmonk","slug":"error-parsing-user-info-claims","errorCode":null,"errorMessage":"error parsing user info claims","messagePattern":"error parsing user info claims","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/auth.go","lineNumber":277,"sourceCode":"\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}\n\t}\n\n\treturn rawIDTk, claims, nil\n}\n\n// Middleware is the HTTP middleware used for wrapping HTTP handlers registered on the echo router.\n// It authorizes token (BasicAuth/token) based and cookie based sessions and on successful auth,\n// sets the authenticated User{} on the echo context on the key UserKey. On failure, it sets an Error{}\n// instead on the same key.\nfunc (o *Auth) Middleware(next echo.HandlerFunc) echo.HandlerFunc {\n\treturn func(c echo.Context) error {\n\t\t// It's an `Authorization` header request.\n\t\thdr := strings.TrimSpace(c.Request().Header.Get(\"Authorization\"))\n\n\t\t// If cookie is set, ignore BasicAuth. This is to preserve backwards compatibility\n\t\t// in v3 -> v4 upgrade where the user browser sessions would still have old\n\t\t// BasicAuth credentials, which no longer work in the new system which expects","sourceCodeStart":259,"sourceCodeEnd":295,"githubUrl":"https://github.com/knadh/listmonk/blob/670c01717d48647093335cc23a6be6f4b79c3b6b/internal/auth/auth.go#L259-L295","documentation":"This error is returned when the claims from the OIDC UserInfo response cannot be unmarshalled into the OIDCclaim struct via userInfo.Claims(&claims). The userinfo response was fetched successfully, but its JSON payload does not fit the expected claim structure. The underlying error is discarded.","triggerScenarios":"claims.Email was empty so the userinfo fallback ran, provider.UserInfo succeeded, but userInfo.Claims(&claims) fails because the userinfo JSON contains fields whose types conflict with OIDCclaim (e.g. email_verified as a string instead of bool, nested objects) or is malformed.","commonSituations":"IdPs that return non-standard userinfo claim types (some return email_verified as \"true\"/\"false\" strings); custom claim transformations or middleware that reshape the response; IdP version upgrades changing the userinfo schema.","solutions":["Log the raw userinfo response (or decode into map[string]interface{}) and compare types against the OIDCclaim struct.","Adjust OIDCclaim field types or json tags to match the IdP's userinfo schema (e.g. use a flexible type for email_verified).","Wrap the underlying err with %w so the JSON unmarshal error is visible.","If possible, request the email scope so the email comes from the ID token and this code path is not needed."],"exampleFix":"// before\nif err := userInfo.Claims(&claims); err != nil {\n\treturn \"\", OIDCclaim{}, errors.New(\"error parsing user info claims\")\n}\n// after\nif err := userInfo.Claims(&claims); err != nil {\n\treturn \"\", OIDCclaim{}, fmt.Errorf(\"error parsing user info claims: %w\", err)\n}","handlingStrategy":"type-guard","validationCode":"// fetch userinfo JSON manually first and sanity-check types\nvar ui map[string]interface{}\njson.NewDecoder(userInfoResp.Body).Decode(&ui)\nif v, ok := ui[\"email_verified\"]; ok {\n\tif _, isBool := v.(bool); !isBool { /* normalize or reject */ }\n}","typeGuard":"func isString(v interface{}) bool { _, ok := v.(string); return ok }\nfunc userinfoMatchesClaimSchema(ui map[string]interface{}) bool {\n\tif e, ok := ui[\"email\"]; ok && !isString(e) { return false }\n\treturn true\n}","tryCatchPattern":"_, claims, err := auth.ExchangeOIDCToken(code, nonce)\nif err != nil {\n\tif strings.Contains(err.Error(), \"error parsing user info claims\") {\n\t\tlog.Printf(\"userinfo claim schema mismatch: %v\", err)\n\t\t// fall back to a sub-based identifier if acceptable\n\t}\n\treturn err\n}","preventionTips":["Test userinfo parsing against each IdP version you deploy.","Use tolerant field types (json.RawMessage or custom UnmarshalJSON) for boolean-like claims.","Keep the email scope granted so this fallback path is uncommon.","Pin and review IdP changelogs for userinfo schema changes."],"tags":["oidc","userinfo-endpoint","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"}