spring-projects/spring-security · error · OAuth2AuthenticationException
invalid_nonce
invalid_nonce
Error message
Invalid nonce
What it means
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.)
Source
Thrown at oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizedClientRefreshedEventListener.java:339
// passed in the authentication request
if (!idToken.getAuthenticatedAt().equals(existingOidcUser.getIdToken().getAuthenticatedAt())
&& (existingOidcUser.getIdToken().getAuthenticatedAt() == null
|| !idToken.getAuthenticatedAt().isAfter(existingOidcUser.getIdToken().getAuthenticatedAt()))) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, "Invalid authenticated at time",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
private void validateNonce(OidcUser existingOidcUser, OidcIdToken idToken) {
if (!StringUtils.hasText(idToken.getNonce())) {
return;
}
if (!Objects.equals(idToken.getNonce(), existingOidcUser.getIdToken().getNonce())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE, "Invalid nonce",
REFRESH_TOKEN_RESPONSE_ERROR_URI);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
}
View on GitHub (pinned to 96852e8860)
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.
Example fix
// before: blindly sending refresh grant to a nonce-dropping provider
// after: detect and force re-login
catch (OAuth2AuthenticationException ex) {
if ("invalid_nonce".equals(ex.getError().getErrorCode())) {
authorizedClientService.removeAuthorizedClient(registrationId, principalName);
}
throw ex;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (existingUser.getIdToken().getNonce() != null
&& newIdToken.getNonce() != null
&& !newIdToken.getNonce().equals(existingUser.getIdToken().getNonce())) {
throw new IllegalStateException("nonce mismatch on refresh");
} Try / catch
try {
listener.onApplicationEvent(event);
} catch (OAuth2AuthenticationException ex) {
if ("invalid_nonce".equals(ex.getError().getErrorCode())) {
// provider drops/alters nonce on refresh: force re-login
authorizedClientService.removeAuthorizedClient(registrationId, principalName);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/f68ee63949a43e55.
Report an issue: GitHub.