{"record":{"id":"71caae2bb8fdf181","repo":"theonedev/onedev","slug":"oidc-error-inconsistent-sub-in-id-token-and-useri","errorCode":null,"errorMessage":"OIDC error: Inconsistent sub in ID token and userinfo","messagePattern":"OIDC error: Inconsistent sub in ID token and userinfo","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"server-plugin/server-plugin-sso-openid/src/main/java/io/onedev/server/plugin/sso/openid/OpenIdConnector.java","lineNumber":267,"sourceCode":"\t\t\t\t\tgroups = null;\n\t\t\t} else {\n\t\t\t\tgroups = null;\n\t\t\t}\n\t\t\t\n\t\t\tvar accessToken = tokenResponse.getOIDCTokens().getBearerAccessToken();\n\t\t\tif (email == null || userName == null || fullName == null \n\t\t\t\t\t|| getGroupsClaim() != null && groups == null) {\n\n\t\t\t\tUserInfoRequest userInfoRequest = new UserInfoRequest(\n\t\t\t\t\t\tnew URI(getCachedProviderMetadata().getUserInfoEndpoint()), accessToken);\n\t\t\t\tvar httpRequest = userInfoRequest.toHTTPRequest();\n\t\t\t\thttpRequest.setSSLSocketFactory(TrustCertsSSLSocketFactory.getDefault());\n\t\t\t\tvar httpResponse = httpRequest.send();\n\n\t\t\t\tif (httpResponse.getStatusCode() == HTTPResponse.SC_OK) {\n\t\t\t\t\tJSONObject json = httpResponse.getBodyAsJSONObject();\n\t\t\t\t\tif (!subject.equals(json.get(\"sub\")))\n\t\t\t\t\t\tthrow new AuthenticationException(_T(\"OIDC error: Inconsistent sub in ID token and userinfo\"));\n\n\t\t\t\t\tif (email == null) {\n\t\t\t\t\t\temail = getStringValue(json.get(\"email\"));\n\t\t\t\t\t\temailVerified = getBooleanValue(json.get(\"email_verified\"));\n\t\t\t\t\t\tif (emailVerified == null)\n\t\t\t\t\t\t\temailVerified = getBooleanValue(json.get(\"emailVerified\"));\n\t\t\t\t\t\tif (emailVerified != null && !emailVerified)\n\t\t\t\t\t\t\temail = null;\n\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\tif (userName == null) \n\t\t\t\t\t\tuserName = getStringValue(json.get(\"preferred_username\"));\n\n\t\t\t\t\tif (fullName == null)\n\t\t\t\t\t\tfullName = getStringValue(json.get(\"name\"));\n\n\t\t\t\t\tif (getGroupsClaim() != null && groups == null) {\n\t\t\t\t\t\tvar jsonArray = (JSONArray) json.get(getGroupsClaim());","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-plugin/server-plugin-sso-openid/src/main/java/io/onedev/server/plugin/sso/openid/OpenIdConnector.java#L249-L285","documentation":"Thrown by OpenIdConnector.processTokenResponse after fetching the userinfo endpoint: the 'sub' (subject) returned by userinfo does not match the 'sub' claim of the ID token. OIDC requires these to be identical; a mismatch signals a misconfigured or compromised provider setup, so authentication is aborted.","triggerScenarios":"The connector calls the userinfo endpoint (over an SSL connection using TrustCertsSSLSocketFactory) and compares json.get(\"sub\") with the ID token subject; the values differ — typically when a proxy/gateway or mismatched client configuration returns a different user, or token/userinfo come from different realms/issuers.","commonSituations":"Identity provider behind a gateway that rewrites or substitutes responses; userinfo URL pointing to a different realm/environment than the token issuer; multiple identity providers (e.g. Keycloak realms) mixed in connector settings; provider bug where userinfo sub is stored as number vs string type difference.","solutions":["Verify the connector's userinfo/discovery configuration points to the same provider and realm that issued the ID token.","Update or fix the identity provider so userinfo returns the same sub as the ID token (same realm, no rewriting proxy).","Check for intermediary proxies/gateways altering the userinfo response body.","Compare sub value types (string vs numeric) returned by the provider and update provider firmware/version if it emits a typed mismatch."],"exampleFix":"// before: token from realm A, userinfo from realm B\nwellKnownConfiguration: \"https://sso.example.com/realms/old/.well-known/openid-configuration\"\n// after\nwellKnownConfiguration: \"https://sso.example.com/realms/current/.well-known/openid-configuration\"","handlingStrategy":"validation","validationCode":"// Pre-check userinfo before wiring the connector:\n// GET userinfo endpoint with a test token; assert response JSON 'sub' equals the ID token's sub.","typeGuard":null,"tryCatchPattern":"try {\n    auth = connector.handleAuthResponse(...);\n} catch (AuthenticationException e) {\n    if (e.getMessage().contains(\"Inconsistent sub\")) {\n        // verify token issuer realm and userinfo endpoint alignment, then retry\n    }\n}","preventionTips":["Ensure discovery/userinfo and token issuance come from the same realm/provider.","Remove or fix proxies that rewrite userinfo responses.","Test the full login with a real account after any provider configuration change.","Keep the identity provider updated to avoid sub type/format bugs."],"tags":["oidc","sso","userinfo","token-validation"],"backgroundTag":"oidc-sub-mismatch","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}