spring-projects/spring-security · error · OAuth2AuthorizationCodeRequestAuthenticationException
access_denied
access_denied
Error message
OAuth 2.0 Parameter: client_id
What it means
OAuth2AuthorizationConsentAuthenticationProvider throws ACCESS_DENIED (parameter: client_id) when the principal submitting the authorization consent is not the same resource-owner principal that originally started the authorization request, or when the authorization can no longer be resolved. The provider removes the stored authorization and rejects the consent submission so the OAuth2 flow cannot be completed on behalf of a different user.
Source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java:224
}
}
Set<GrantedAuthority> authorities = new HashSet<>();
authorizationConsentBuilder.authorities(authorities::addAll);
if (authorities.isEmpty()) {
// Authorization consent denied (or revoked)
if (currentAuthorizationConsent != null) {
this.authorizationConsentService.remove(currentAuthorizationConsent);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Revoked authorization consent");
}
}
this.authorizationService.remove(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Removed authorization");
}
throw createException(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID,
authorizationConsentAuthentication, registeredClient, authorizationRequest);
}
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
if (currentAuthorizationConsent == null || !authorizationConsent.equals(currentAuthorizationConsent)) {
this.authorizationConsentService.save(authorizationConsent);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization consent");
}
}
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationConsentAuthentication,
registeredClient, authorization, authorizedScopes);
OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext);
if (authorizationCode == null) {
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
"The token generator failed to generate the authorization code.", ERROR_URI);
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);View on GitHub (pinned to 96852e8860)
Solutions
- Ensure the same authenticated principal completes the consent flow that started the authorization request — re-initiate the authorization request if the user changed
- Verify session affinity/persistence so the user session is not replaced or shared between different identities during the flow
- Check custom Authentication/Principal mapping (e.g. OAuth2Authentication) so principal comparison in the provider matches your app's identity model
- Clear stale authorizations from the OAuth2AuthorizationService if authorizations were pre-seeded with mismatched principal data
Example fix
// before: replaying consent from a different session curl -X POST /oauth2/consent -d 'client_id=messaging-client&state=abc&scope=message.read' // after: re-run the authorization request with the current user so principal matches GET /oauth2/authorize?response_type=code&client_id=messaging-client&scope=message.read&redirect_uri=...
Defensive patterns
Strategy: validation
Validate before calling
if (!currentUser.getPrincipal().equals(originalAuthorization.getPrincipalName())) {
throw new IllegalStateException("Consent must be submitted by the principal that started the authorization");
} Type guard
boolean samePrincipal(Authentication current, OAuth2Authorization auth) {
return current != null && auth != null
&& current.getName().equals(auth.getPrincipalName());
} Prevention
- Keep the user session stable for the duration of the OAuth2 authorization flow
- Avoid switching authenticated users mid-flow (login screens inside consent flows)
- Use a shared, persistent OAuth2AuthorizationService in clustered deployments
- Re-initiate the authorization request when the principal changes
When it happens
Trigger: Calling the authorization consent endpoint (OAuth2AuthorizationConsentAuthenticationToken processed by this provider) when the currently authenticated principal differs from the principal recorded in the stored OAuth2Authorization for the given client_id and state, causing the authorization to be removed and access_denied to be returned.
Common situations: Users logged in as a different account than the one that initiated the OAuth2 authorization (e.g. session switched mid-flow, multiple browser tabs with different sessions, SSO re-authentication as another user); load-balanced deployments where the authorizationService lookup returns an authorization owned by another principal; tests replaying a consent request without the original user's session.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/1319e7295c75d591.
Report an issue: GitHub.