spring-projects/spring-security · warning · OAuth2AuthenticationException
access_denied
access_denied
Error message
OAuth 2.0 Parameter: client_id
What it means
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.
Source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceAuthorizationConsentAuthenticationProvider.java:203
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");
}
}
authorization = OAuth2Authorization.from(authorization)
.invalidate(deviceCodeToken.getToken())
.invalidate(userCodeToken.getToken())
.attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE))
.build();
this.authorizationService.save(authorization);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Invalidated device code and user code because authorization consent was denied");
}
throw createException(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID);
}
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
if (currentAuthorizationConsent == null || !authorizationConsent.equals(currentAuthorizationConsent)) {
this.authorizationConsentService.save(authorizationConsent);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Saved authorization consent");
}
}
authorization = OAuth2Authorization.from(authorization)
.authorizedScopes(authorizedScopes)
.invalidate(userCodeToken.getToken())
.attributes((attrs) -> attrs.remove(OAuth2ParameterNames.STATE))
.attributes((attrs) -> attrs.remove(OAuth2ParameterNames.SCOPE))
.build();
this.authorizationService.save(authorization);
View on GitHub (pinned to 96852e8860)
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.
Example fix
// before: device client keeps polling after denial
// while (true) { post token endpoint; }
// after
// if (response.error === 'access_denied') { showError('Authorization denied'); exit; } Defensive patterns
Strategy: try-catch
Try / catch
catch (OAuth2AuthenticationException e) { if ("access_denied".equals(e.getError().getErrorCode())) { stopTokenPolling(); showDeniedMessageOnDevice(); } } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- server_error
- OAuth 2.0 Parameter: + parameterName
- Invalidated device code used by registered client '%s'
- Invalidated user code used by registered client '%s'
- access_denied
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/ce355c34893fdbd8.
Report an issue: GitHub.