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

  1. Generate a fresh device authorization (call /device_authorization endpoint again) and have the user enter the new user code
  2. Check for duplicate form submissions or replays of the verification request on the verification endpoint
  3. 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

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


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/82d915267e5bf478. Report an issue: GitHub.