{"record":{"id":"f68ee63949a43e55","repo":"spring-projects/spring-security","slug":"invalid-nonce-f68ee6","errorCode":"invalid_nonce","errorMessage":"Invalid nonce","messagePattern":"Invalid nonce","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java","lineNumber":339,"sourceCode":"\t\t// passed in the authentication request\n\t\tif (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())\n\t\t\t\t&& (existingOidcUser.getIdToken().getAuthenticatedAt() == null\n\t\t\t\t\t\t|| !idToken.getAuthenticatedAt().isAfter(existingOidcUser.getIdToken().getAuthenticatedAt()))) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, \"Invalid authenticated at time\",\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 validateNonce(OidcUser existingOidcUser, OidcIdToken idToken) {\n\t\tif (!StringUtils.hasText(idToken.getNonce())) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!Objects.equals(idToken.getNonce(), existingOidcUser.getIdToken().getNonce())) {\n\t\t\tOAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE, \"Invalid nonce\",\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}\n","sourceCodeStart":321,"sourceCodeEnd":344,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java#L321-L344","documentation":"When the original ID Token carried a nonce, OIDC Core 12.2 requires the refreshed ID Token to carry the same nonce. This listener throws OAuth2AuthenticationException with error code 'invalid_nonce' and message 'Invalid nonce' when the new token's nonce differs from the existing token's nonce. (If the new token has no nonce at all, validation is skipped by design.)","triggerScenarios":"validateIdToken -> validateNonce when the existing id_token has a nonce and the refreshed id_token's nonce is present but different — typically the IdP echoed a different or empty nonce string on the refresh grant.","commonSituations":"IdP does not preserve nonce on the refresh_token grant (some providers return a nonce-less or differently-valued nonce); session was created with nonce but the provider's refresh implementation regenerates it; misconfigured nonce handling in a custom authentication converter.","solutions":["Confirm the provider supports nonce continuity on refresh — check its docs; some IdPs never echo nonce on refresh and the workaround is provider-specific.","Verify your session/nonce store returns the same original nonce value when comparing (not a re-hashed value).","If the provider legitimately omits nonce on refresh, note that this check is skipped for absent nonces — the failure means a different value was actually returned; report to the provider or upgrade its version.","As a last resort, re-authenticate the user fully rather than weakening the check."],"exampleFix":"// before: blindly sending refresh grant to a nonce-dropping provider\n// after: detect and force re-login\ncatch (OAuth2AuthenticationException ex) {\n    if (\"invalid_nonce\".equals(ex.getError().getErrorCode())) {\n        authorizedClientService.removeAuthorizedClient(registrationId, principalName);\n    }\n    throw ex;\n}","handlingStrategy":"try-catch","validationCode":"if (existingUser.getIdToken().getNonce() != null\n    && newIdToken.getNonce() != null\n    && !newIdToken.getNonce().equals(existingUser.getIdToken().getNonce())) {\n    throw new IllegalStateException(\"nonce mismatch on refresh\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    listener.onApplicationEvent(event);\n} catch (OAuth2AuthenticationException ex) {\n    if (\"invalid_nonce\".equals(ex.getError().getErrorCode())) {\n        // provider drops/alters nonce on refresh: force re-login\n        authorizedClientService.removeAuthorizedClient(registrationId, principalName);\n    }\n}","preventionTips":["Check IdP docs for nonce behavior on refresh_token grant before enabling nonce","Store and compare the original raw nonce, not a hashed variant","Track provider versions that fix nonce continuity","Plan a re-authentication fallback path rather than bypassing the check"],"tags":["oauth2","oidc","nonce","token-refresh"],"backgroundTag":"oidc-nonce-mismatch","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}