{"record":{"id":"1319e7295c75d591","repo":"spring-projects/spring-security","slug":"access-denied-1319e7","errorCode":"access_denied","errorMessage":"OAuth 2.0 Parameter: client_id","messagePattern":"OAuth 2\\.0 Parameter: client_id","errorType":"error_code","errorClass":"OAuth2AuthorizationCodeRequestAuthenticationException","httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java","lineNumber":224,"sourceCode":"\t\t\t}\n\t\t}\n\n\t\tSet<GrantedAuthority> authorities = new HashSet<>();\n\t\tauthorizationConsentBuilder.authorities(authorities::addAll);\n\n\t\tif (authorities.isEmpty()) {\n\t\t\t// Authorization consent denied (or revoked)\n\t\t\tif (currentAuthorizationConsent != null) {\n\t\t\t\tthis.authorizationConsentService.remove(currentAuthorizationConsent);\n\t\t\t\tif (this.logger.isTraceEnabled()) {\n\t\t\t\t\tthis.logger.trace(\"Revoked authorization consent\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.authorizationService.remove(authorization);\n\t\t\tif (this.logger.isTraceEnabled()) {\n\t\t\t\tthis.logger.trace(\"Removed authorization\");\n\t\t\t}\n\t\t\tthrow createException(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID,\n\t\t\t\t\tauthorizationConsentAuthentication, registeredClient, authorizationRequest);\n\t\t}\n\n\t\tOAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();\n\t\tif (currentAuthorizationConsent == null || !authorizationConsent.equals(currentAuthorizationConsent)) {\n\t\t\tthis.authorizationConsentService.save(authorizationConsent);\n\t\t\tif (this.logger.isTraceEnabled()) {\n\t\t\t\tthis.logger.trace(\"Saved authorization consent\");\n\t\t\t}\n\t\t}\n\n\t\tOAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationConsentAuthentication,\n\t\t\t\tregisteredClient, authorization, authorizedScopes);\n\t\tOAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext);\n\t\tif (authorizationCode == null) {\n\t\t\tOAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,\n\t\t\t\t\t\"The token generator failed to generate the authorization code.\", ERROR_URI);\n\t\t\tthrow new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationConsentAuthenticationProvider.java#L206-L242","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: replaying consent from a different session\ncurl -X POST /oauth2/consent -d 'client_id=messaging-client&state=abc&scope=message.read'\n// after: re-run the authorization request with the current user so principal matches\nGET /oauth2/authorize?response_type=code&client_id=messaging-client&scope=message.read&redirect_uri=...","handlingStrategy":"validation","validationCode":"if (!currentUser.getPrincipal().equals(originalAuthorization.getPrincipalName())) {\n    throw new IllegalStateException(\"Consent must be submitted by the principal that started the authorization\");\n}","typeGuard":"boolean samePrincipal(Authentication current, OAuth2Authorization auth) {\n    return current != null && auth != null\n        && current.getName().equals(auth.getPrincipalName());\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["oauth2","authorization-consent","access-denied","session-mismatch"],"backgroundTag":"authentication-required","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}