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

  1. Ensure the same authenticated principal completes the consent flow that started the authorization request — re-initiate the authorization request if the user changed
  2. Verify session affinity/persistence so the user session is not replaced or shared between different identities during the flow
  3. Check custom Authentication/Principal mapping (e.g. OAuth2Authentication) so principal comparison in the provider matches your app's identity model
  4. 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

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.