spring-projects/spring-security · error · OAuth2AuthorizationException
OAuth2Error from authorization response error (dynamic)
Error message
OAuth2Error from authorization response error (dynamic)
What it means
OAuth2AuthorizationCodeAuthenticationProvider handles the OAuth2 authorization-code callback. If the authorization response reports an error (statusError), it throws an OAuth2AuthorizationException carrying that error verbatim; if the state parameter does not match the original authorization request, it throws an OAuth2AuthorizationException with invalid_state_parameter. The message is dynamic because it comes from the authorization server's error redirect.
Source
Thrown at oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java:81
* provided parameters.
* @param accessTokenResponseClient the client used for requesting the access token
* credential from the Token Endpoint
*/
public OAuth2AuthorizationCodeAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
this.accessTokenResponseClient = accessTokenResponseClient;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
OAuth2Error error = authorizationResponse.getError();
Assert.notNull(error, "error cannot be null when status is error");
throw new OAuth2AuthorizationException(error);
}
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
if (!Objects.equals(authorizationResponse.getState(), authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthorizationException(oauth2Error);
}
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
OAuth2AuthorizationCodeAuthenticationToken authenticationResult = new OAuth2AuthorizationCodeAuthenticationToken(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(),
accessTokenResponse.getRefreshToken(), accessTokenResponse.getAdditionalParameters());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
}
View on GitHub (pinned to 96852e8860)
Solutions
- Check the OAuth2Error error code in the exception — access_denied etc. is a user/provider decision, not a bug; handle it as a normal cancel.
- For invalid_state_parameter, ensure the OAuth2AuthorizationRequestRepository (session/cookie) persists across the redirect: enable sticky sessions or a shared session store in multi-instance deployments.
- Verify cookies are not blocked/stripped (SameSite/secure settings, proxy configuration) so state survives the round trip.
- Confirm redirect-uri and provider metadata are correct so the callback hits the same app instance/session that started the flow.
Example fix
// before: in-memory session lost behind LB with multiple instances // after: sticky sessions or distributed session // Spring Boot spring.session.store-type=redis // or at the LB: enable session affinity (sticky cookie) for the app instances
Defensive patterns
Strategy: try-catch
Validate before calling
// validate the callback before invoking the provider
OAuth2AuthorizationResponse resp = authorizationExchange.getAuthorizationResponse();
boolean callbackOk = !resp.statusError()
&& Objects.equals(resp.getState(), authorizationExchange.getAuthorizationRequest().getState()); Type guard
boolean isRecoverableAuthorizationError(OAuth2AuthorizationException ex) {
return "access_denied".equals(ex.getError().getErrorCode())
|| "invalid_state_parameter".equals(ex.getError().getErrorCode());
} Try / catch
try {
return authenticationManager.authenticate(authorizationCodeAuthentication);
} catch (OAuth2AuthorizationException ex) {
if ("access_denied".equals(ex.getError().getErrorCode())) {
// user declined consent — show friendly cancel page
} else if ("invalid_state_parameter".equals(ex.getError().getErrorCode())) {
// restart the login flow; check session stickiness/cookies
}
} Prevention
- Use sticky sessions or a distributed session/AuthorizationRequestRepository in multi-instance deployments.
- Ensure cookies survive the redirect (correct SameSite/secure/proxy settings).
- Handle access_denied gracefully — it is a normal user action, not a system fault.
When it happens
Trigger: authenticate(...) receives an OAuth2AuthorizationCodeAuthenticationToken whose authorization response has statusError() (e.g. error=access_denied) or whose state differs from the state stored in the authorization request; the provider throws OAuth2AuthorizationException with the upstream error or invalid_state_parameter.
Common situations: User denies consent at the provider (access_denied redirect); app restarted/scaled to another instance between request and callback so the AuthorizationRequestRepository (usually HttpSession) lost the stored state; load balancer without sticky sessions; cookies blocked, dropping the state attribute; registering the wrong redirect URI so the provider receives a malformed callback.
Related errors
- OAuth2Error from upstream authorization exception (dynamic)
- Invalidated authorization code used by registered client '%s
- Unable to create an {OAuth2AuthorizedClientManager} bean. Ex
- invalid_dpop_proof
- invalid_scope
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/5ae0944ef3efd793.
Report an issue: GitHub.