{"record":{"id":"266ed6a2c87082f6","repo":"spring-projects/spring-security","slug":"invalid-id-token","errorCode":"invalid_id_token","errorMessage":"Invalid issuer","messagePattern":"Invalid issuer","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler.java","lineNumber":237,"sourceCode":"\t\tvalidateAudience(existingOidcUser, idToken);\n\t\t// if the ID Token contains an auth_time Claim, its value MUST represent the time\n\t\t// of the original authentication - not the time that the new ID token is issued,\n\t\tvalidateAuthenticatedAt(existingOidcUser, idToken);\n\t\t// it SHOULD NOT have a nonce Claim, even when the ID Token issued at the time of\n\t\t// the original authentication contained nonce; however, if it is present, its\n\t\t// value MUST be the same as in the ID Token issued at the time of the original\n\t\t// authentication,\n\t\tvalidateNonce(existingOidcUser, idToken);\n\t}\n\n\tprivate void validateIssuer(OidcUser existingOidcUser, OidcIdToken idToken) {\n\t\tURL idTokenIssuer = idToken.getIssuer();\n\t\tURL existingIdTokenIssuer = existingOidcUser.getIdToken().getIssuer();\n\t\tif (idTokenIssuer == null || existingIdTokenIssuer == null\n\t\t\t\t|| !idTokenIssuer.toString().equals(existingIdTokenIssuer.toString())) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, \"Invalid issuer\",\n\t\t\t\t\tREFRESH_TOKEN_RESPONSE_ERROR_URI);\n\t\t\tthrow new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());\n\t\t}\n\t}\n\n\tprivate void validateSubject(OidcUser existingOidcUser, OidcIdToken idToken) {\n\t\tif (!Objects.equals(idToken.getSubject(), existingOidcUser.getIdToken().getSubject())) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, \"Invalid subject\",\n\t\t\t\t\tREFRESH_TOKEN_RESPONSE_ERROR_URI);\n\t\t\tthrow new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());\n\t\t}\n\t}\n\n\tprivate void validateIssuedAt(OidcUser existingOidcUser, OidcIdToken idToken) {\n\t\tInstant idTokenIssuedAt = idToken.getIssuedAt();\n\t\tInstant existingIdTokenIssuedAt = existingOidcUser.getIdToken().getIssuedAt();\n\t\tif (idTokenIssuedAt == null || existingIdTokenIssuedAt == null\n\t\t\t\t|| !idTokenIssuedAt.isAfter(existingIdTokenIssuedAt.minus(this.clockSkew))) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, \"Invalid issued at time\",\n\t\t\t\t\tREFRESH_TOKEN_RESPONSE_ERROR_URI);","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler.java#L219-L255","documentation":"When an OIDC session's tokens are refreshed, RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler validates the newly received ID token against the previously stored OidcUser before updating the security context. If either the new or the existing ID token's issuer is missing, or the two issuer values differ, it throws an OAuth2AuthenticationException with the invalid_id_token error, since issuer mismatch indicates the token may not come from the trusted provider.","triggerScenarios":"A refresh-token flow yields a new OidcIdToken whose iss claim differs from (or is absent compared to) the issuer recorded on the existing session's OidcUser, during validateIdToken inside the reactive authorization success handler.","commonSituations":"Identity provider reconfigured with a new issuer URL (different scheme/host/path) mid-session; sessions surviving a provider migration or regional endpoint change; misconfigured issuer in provider metadata vs application config; tokens minted by a different realm/environment (e.g. dev vs prod issuer) sharing the same client.","solutions":["Ensure the IDP always issues the same iss value that matches your configured issuer/registration; fix provider or realm configuration.","If the issuer legitimately changed (migration), force users to re-authenticate (invalidate sessions) rather than refreshing old tokens.","Verify issuer URI formatting (trailing slash, http vs https, host aliases) is identical between config and the token's iss claim.","Subclass the handler to customize or relax issuer comparison only if your deployment genuinely supports multiple issuers."],"exampleFix":"// before: realm renamed, old sessions refresh against new issuer\n// after: pin issuer and rotate sessions on change\n// application.yml\nspring.security.oauth2.client.provider.myidp.issuer-uri=https://idp.example.com/realms/stable\n// plus a migration plan that invalidates existing sessions","handlingStrategy":"try-catch","validationCode":"boolean sameIssuer = newIdToken.getIssuer() != null\n    && existingOidcUser.getIdToken().getIssuer() != null\n    && newIdToken.getIssuer().toString().equals(existingOidcUser.getIdToken().getIssuer().toString());","typeGuard":"boolean issuerMatches(OidcIdToken fresh, OidcIdToken stored) {\n    return fresh.getIssuer() != null && stored.getIssuer() != null\n        && fresh.getIssuer().toString().equals(stored.getIssuer().toString());\n}","tryCatchPattern":"try {\n    handler.onAuthenticationSuccess(exchange, authentication);\n} catch (OAuth2AuthenticationException ex) {\n    if (\"invalid_id_token\".equals(ex.getError().getErrorCode())) {\n        // invalidate session and restart authorization code flow\n    }\n}","preventionTips":["Pin the issuer URI in configuration and alert when the IDP's .well-known/openid-configuration issuer changes.","Avoid sharing one client registration across realms/environments with different issuers.","Plan issuer migrations to force re-login rather than silent refresh."],"tags":["oauth2","oidc","id-token","spring-security","reactive"],"backgroundTag":"jwt-issuer-mismatch","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}