spring-projects/spring-security · error · OAuth2AuthenticationException
OAuth2Error from upstream authorization exception (dynamic)
Error message
OAuth2Error from upstream authorization exception (dynamic)
What it means
OAuth2LoginAuthenticationProvider.authenticate() delegates token exchange and user loading to downstream providers. When any of them throws an OAuth2AuthorizationException, it is re-wrapped as an OAuth2AuthenticationException carrying the upstream OAuth2Error (its toString() becomes the message). This is the normal failure funnel for Spring Security's OAuth2 login flow: the concrete cause is inside the wrapped error/exception.
Source
Thrown at oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java:122
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
if (loginAuthenticationToken.getAuthorizationExchange()
.getAuthorizationRequest()
.getScopes()
.contains("openid")) {
// This is an OpenID Connect Authentication Request so return null
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
return null;
}
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken;
try {
authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider
.authenticate(
new OAuth2AuthorizationCodeAuthenticationToken(loginAuthenticationToken.getClientRegistration(),
loginAuthenticationToken.getAuthorizationExchange()));
}
catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
Assert.notNull(accessToken, "accessToken cannot be null");
Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
Assert.notNull(oauth2User, "oauth2User cannot be null");
Collection<GrantedAuthority> authorities = new HashSet<>(oauth2User.getAuthorities());
Collection<GrantedAuthority> mappedAuthorities = new LinkedHashSet<>(
this.authoritiesMapper.mapAuthorities(authorities));
mappedAuthorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(),
oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken());
authenticationResult.setDetails(loginAuthenticationToken.getDetails());
return authenticationResult;
}
View on GitHub (pinned to 96852e8860)
Solutions
- Inspect the wrapped OAuth2AuthenticationException's getError() (errorCode, description, uri) and cause to identify the real upstream failure.
- Verify the client registration (issuer URI, client-id/secret, redirect-uri, scopes) in your OAuth2Login configuration.
- Check that the authorization request completed in one session (state/cookies preserved) and the code is not replayed.
- If userinfo fails, hit the userinfo endpoint manually with the access token to see the raw error.
Example fix
// before: opaque 500 on login
// after: log the concrete OAuth2Error
try {
authenticationManager.authenticate(token);
}
catch (OAuth2AuthenticationException ex) {
logger.warn("OAuth2 login failed: code={}, desc={}", ex.getError().getErrorCode(), ex.getError().getDescription(), ex);
throw ex;
} Defensive patterns
Strategy: try-catch
Try / catch
try { auth = authenticationManager.authenticate(token); } catch (OAuth2AuthenticationException ex) { OAuth2Error err = ex.getError(); log.warn("login failed: {} - {}", err.getErrorCode(), err.getDescription(), ex); throw new LoginFailureException(err.getErrorCode(), ex); } Prevention
- Log ex.getError().getErrorCode() and getDescription() so the underlying cause is visible.
- Keep cookies/session intact across the authorization redirect (state + code live in the session).
- Test the full redirect flow with curl/browser devtools, not just unit tests.
- Keep client registration values in sync with the provider's well-known configuration.
When it happens
Trigger: Thrown when the inner OAuth2AuthorizationCodeAuthenticationProvider (invoked via AuthenticationManager.authenticate(new OAuth2AuthorizationCodeAuthenticationToken(...))) fails during the authorization-code token exchange, or when userService.loadUser() fails, and an OAuth2AuthorizationException propagates to this catch block.
Common situations: Authorization server rejects the code (expired/already-used code), token endpoint returns an error JSON, token response is malformed, client authentication fails (bad client_secret or JWK), or the user-info endpoint returns an error. Frequently hit during local dev when redirect URI/state handling is wrong.
Related errors
- oidc_provider_not_configured
- OAuth2Error from authorization response error (dynamic)
- Invalidated authorization code used by registered client '%s
- AuthenticationException must be of type %s but was %s
- RunAsImplAuthenticationProvider.incorrectKey
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/33279846f6ad9443.
Report an issue: GitHub.