{"record":{"id":"ce355c34893fdbd8","repo":"spring-projects/spring-security","slug":"access-denied-ce355c","errorCode":"access_denied","errorMessage":"OAuth 2.0 Parameter: client_id","messagePattern":"OAuth 2\\.0 Parameter: client_id","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":null,"severity":"warning","filePath":"oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java","lineNumber":203,"sourceCode":"\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\tauthorization = OAuth2Authorization.from(authorization)\n\t\t\t\t.invalidate(deviceCodeToken.getToken())\n\t\t\t\t.invalidate(userCodeToken.getToken())\n\t\t\t\t.attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE))\n\t\t\t\t.build();\n\t\t\tthis.authorizationService.save(authorization);\n\t\t\tif (this.logger.isTraceEnabled()) {\n\t\t\t\tthis.logger.trace(\"Invalidated device code and user code because authorization consent was denied\");\n\t\t\t}\n\t\t\tthrow createException(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID);\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\tauthorization = OAuth2Authorization.from(authorization)\n\t\t\t.authorizedScopes(authorizedScopes)\n\t\t\t.invalidate(userCodeToken.getToken())\n\t\t\t.attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE))\n\t\t\t.attributes((attrs) -> attrs.remove(OAuth2ParameterNames.SCOPE))\n\t\t\t.build();\n\t\tthis.authorizationService.save(authorization);\n","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java#L185-L221","documentation":"In OAuth2DeviceAuthorizationConsentAuthenticationProvider.authenticate, when the user denies the device authorization consent, the provider invalidates the device code and user code, saves the updated authorization, and throws an OAuth2AuthenticationException with error code access_denied (parameter client_id). This is the standard RFC 8628 denial path: the resource owner explicitly declined the device's access request.","triggerScenarios":"The user clicks 'Deny' on the device consent page; the consent request resolves to an existing authorization but the consent parameter indicates denial (deviceAuthorizationConsentAuthentication.isConsent... / denial branch), causing the access_denied exception after cleanup of device/user codes.","commonSituations":"An end user deliberately denies an app's device login attempt (expected behavior); a device client polling the token endpoint then receives authorization_pending/access_denied at the token endpoint; automated tests exercise the deny path; a confusing consent page causes users to accidentally deny.","solutions":["This is expected user behavior: treat access_denied at the token endpoint as final and stop polling; show a 'authorization was denied' message on the device.","To proceed, restart the entire device authorization flow so the user can approve.","Check the client handles OAuth2ErrorCodes.ACCESS_DENIED from the token endpoint gracefully instead of retrying.","Review consent page UX if users report accidental denials."],"exampleFix":"// before: device client keeps polling after denial\n// while (true) { post token endpoint; }\n// after\n// if (response.error === 'access_denied') { showError('Authorization denied'); exit; }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (OAuth2AuthenticationException e) { if (\"access_denied\".equals(e.getError().getErrorCode())) { stopTokenPolling(); showDeniedMessageOnDevice(); } }","preventionTips":["Stop polling the token endpoint on access_denied","Treat denial as final; restart flow only on user request","Handle the error at the token endpoint gracefully in device clients"],"tags":["oauth2","device-flow","access-denied","user-consent"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}