spring-projects/spring-security · error
Invalidated user code used by registered client '%s'
Error message
Invalidated user code used by registered client '%s'
What it means
During device authorization flow verification, the supplied user code exists but is no longer active (expired or already invalidated). The provider invalidates the token persistently and logs a warning naming the registered client that used it, then rejects the request with an OAuth2 INVALID_GRANT error.
Source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java:125
OAuth2Authorization authorization = this.authorizationService
.findByToken(deviceVerificationAuthentication.getUserCode(), USER_CODE_TOKEN_TYPE);
if (authorization == null) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Retrieved authorization with user code");
}
OAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);
Assert.notNull(userCode, "userCode cannot be null");
if (!userCode.isActive()) {
if (!userCode.isInvalidated()) {
authorization = OAuth2Authorization.from(authorization).invalidate(userCode.getToken()).build();
this.authorizationService.save(authorization);
if (this.logger.isWarnEnabled()) {
this.logger.warn(LogMessage.format("Invalidated user code used by registered client '%s'",
authorization.getRegisteredClientId()));
}
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
Authentication principal = (Authentication) deviceVerificationAuthentication.getPrincipal();
if (!isPrincipalAuthenticated(principal)) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not authenticate device verification request since principal not authenticated");
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
}
RegisteredClient registeredClient = this.registeredClientRepository
.findById(authorization.getRegisteredClientId());
Assert.notNull(registeredClient, "registeredClient cannot be null");
View on GitHub (pinned to 96852e8860)
Solutions
- Generate a fresh device authorization (call /device_authorization endpoint again) and have the user enter the new user code
- Check for duplicate form submissions or replays of the verification request on the verification endpoint
- Check the device code lifetime configuration (DeviceAuthorization endpoint token lifetime) so codes do not expire before the user completes verification
Example fix
// before: resubmitting the old user code provider.authenticate(new OAuth2DeviceVerificationAuthenticationToken(principal, oldUserCode, state)); // after: obtain a fresh device authorization first OAuth2DeviceAuthorizationResponse da = client.deviceAuthorization(); // show da.userCode() to user, then verify with that code provider.authenticate(new OAuth2DeviceVerificationAuthenticationToken(principal, da.userCode(), da.state()));
Defensive patterns
Strategy: validation
Validate before calling
if (!userCode.isActive()) {
if (userCode.isInvalidated()) {
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_grant", "user code already consumed", null));
}
// expired-but-not-invalidated: let provider handle invalidation
}
Type guard
boolean isReusableUserCode(OAuth2UserCode code) { return code != null && code.isActive(); } Try / catch
try {
provider.authenticate(verificationToken);
} catch (OAuth2AuthenticationException e) {
if (OAuth2ErrorCodes.INVALID_GRANT.equals(e.getError().getErrorCode())) {
// restart device flow: new device authorization request
}
} Prevention
- Disable double-submit of the verification form (CSRF token / idempotency)
- Use a one-time redirect after consent so refresh cannot replay the code
- Set device code/user code lifetimes long enough for the user to complete verification
When it happens
Trigger: Calling OAuth2DeviceVerificationAuthenticationProvider.authenticate() with a user code that has been previously consumed, or that expired, or that was invalidated via authorization invalidation (e.g. token revocation).
Common situations: User re-submits a device code after already approving the consent page; double form submission; device code expired due to long user delay before visiting the verification URI.
Related errors
- Invalidated authorization code used by registered client '%s
- Invalidated authorization token(s) previously issued to regi
- Invalidated device code used by registered client '%s'
- Unable to create an {OAuth2AuthorizedClientManager} bean. Ex
- invalid_dpop_proof
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/82d915267e5bf478.
Report an issue: GitHub.