spring-projects/spring-security · error · OAuth2AuthenticationException
OAuth2Error from upstream authorization exception (dynamic)
Error message
OAuth2Error from upstream authorization exception (dynamic)
What it means
OidcAuthorizationCodeAuthenticationProvider.getResponse() calls the configured accessTokenResponseClient to exchange the code. Any OAuth2AuthorizationException raised there (network error, invalid_token_response, token endpoint rejection) is converted into an OAuth2AuthenticationException whose message is the upstream OAuth2Error's toString. The real cause is in the wrapped error/cause.
Source
Thrown at oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java:183
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oidcUser.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,
accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
}
private OAuth2AccessTokenResponse getResponse(OAuth2LoginAuthenticationToken authorizationCodeAuthentication) {
try {
return this.accessTokenResponseClient.getTokenResponse(
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
}
catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
}
private void validateNonce(OAuth2AuthorizationRequest authorizationRequest, OidcIdToken idToken) {
String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
if (requestNonce == null) {
return;
}
String nonceHash = getNonceHash(requestNonce);
String nonceHashClaim = idToken.getNonce();
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
private String getNonceHash(String requestNonce) {
try {View on GitHub (pinned to 96852e8860)
Solutions
- Inspect ex.getError() (code + description) — typical values are invalid_grant, invalid_client, invalid_token_response.
- For invalid_grant: prevent code reuse/replays (don't re-post the login callback) and check clock skew between app and OP.
- For invalid_client: fix client credentials and clientAuthenticationMethod.
- For invalid_token_response: verify the token endpoint returns proper JSON (see empty/malformed response fixes).
Example fix
// before: user refreshes callback URL -> invalid_grant surfaces as 500
// after: catch and redirect to a re-login flow
catch (OAuth2AuthenticationException ex) {
if ("invalid_grant".equals(ex.getError().getErrorCode())) {
return "redirect:/oauth2/authorization/" + registrationId;
}
throw ex;
} Defensive patterns
Strategy: try-catch
Try / catch
catch (OAuth2AuthenticationException ex) {
String code = ex.getError().getErrorCode();
if ("invalid_grant".equals(code)) { return "redirect:/oauth2/authorization/" + registrationId; }
if ("invalid_client".equals(code)) { throw new ConfigurationException("Check client credentials/auth method", ex); }
throw ex;
} Prevention
- Never let users bookmark/refresh the OAuth2 callback URL; codes are single-use.
- Keep app and OP clocks synchronized (NTP).
- Align clientAuthenticationMethod and credentials with the OP's requirements.
- Monitor token-endpoint error codes to distinguish config bugs from transient failures.
When it happens
Trigger: Thrown in getResponse() (invoked from authenticate via accessTokenResponse) when the access-token request for the OIDC authorization-code grant fails: HTTP error from the token endpoint, empty body, connection failure, or client-authentication failure.
Common situations: Wrong token URI, expired/already-redeemed authorization code (browser refresh or back-button), client authentication mismatch (basic vs post vs private_key_jwt), or the OP returning invalid_grant.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/44b49b3900931362.
Report an issue: GitHub.