{"record":{"id":"82d915267e5bf478","repo":"spring-projects/spring-security","slug":"invalidated-user-code-used-by-registered-client","errorCode":null,"errorMessage":"Invalidated user code used by registered client '%s'","messagePattern":"Invalidated user code used by registered client '(.+?)'","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java","lineNumber":125,"sourceCode":"\n\t\tOAuth2Authorization authorization = this.authorizationService\n\t\t\t.findByToken(deviceVerificationAuthentication.getUserCode(), USER_CODE_TOKEN_TYPE);\n\t\tif (authorization == null) {\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);\n\t\t}\n\n\t\tif (this.logger.isTraceEnabled()) {\n\t\t\tthis.logger.trace(\"Retrieved authorization with user code\");\n\t\t}\n\n\t\tOAuth2Authorization.Token<OAuth2UserCode> userCode = authorization.getToken(OAuth2UserCode.class);\n\t\tAssert.notNull(userCode, \"userCode cannot be null\");\n\t\tif (!userCode.isActive()) {\n\t\t\tif (!userCode.isInvalidated()) {\n\t\t\t\tauthorization = OAuth2Authorization.from(authorization).invalidate(userCode.getToken()).build();\n\t\t\t\tthis.authorizationService.save(authorization);\n\t\t\t\tif (this.logger.isWarnEnabled()) {\n\t\t\t\t\tthis.logger.warn(LogMessage.format(\"Invalidated user code used by registered client '%s'\",\n\t\t\t\t\t\t\tauthorization.getRegisteredClientId()));\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);\n\t\t}\n\n\t\tAuthentication principal = (Authentication) deviceVerificationAuthentication.getPrincipal();\n\t\tif (!isPrincipalAuthenticated(principal)) {\n\t\t\tif (this.logger.isTraceEnabled()) {\n\t\t\t\tthis.logger.trace(\"Did not authenticate device verification request since principal not authenticated\");\n\t\t\t}\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);\n\t\t}\n\n\t\tRegisteredClient registeredClient = this.registeredClientRepository\n\t\t\t.findById(authorization.getRegisteredClientId());\n\t\tAssert.notNull(registeredClient, \"registeredClient cannot be null\");\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceVerificationAuthenticationProvider.java#L107-L143","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before: resubmitting the old user code\nprovider.authenticate(new OAuth2DeviceVerificationAuthenticationToken(principal, oldUserCode, state));\n// after: obtain a fresh device authorization first\nOAuth2DeviceAuthorizationResponse da = client.deviceAuthorization();\n// show da.userCode() to user, then verify with that code\nprovider.authenticate(new OAuth2DeviceVerificationAuthenticationToken(principal, da.userCode(), da.state()));","handlingStrategy":"validation","validationCode":"if (!userCode.isActive()) {\n    if (userCode.isInvalidated()) {\n        throw new OAuth2AuthenticationException(new OAuth2Error(\"invalid_grant\", \"user code already consumed\", null));\n    }\n    // expired-but-not-invalidated: let provider handle invalidation\n}\n","typeGuard":"boolean isReusableUserCode(OAuth2UserCode code) { return code != null && code.isActive(); }","tryCatchPattern":"try {\n    provider.authenticate(verificationToken);\n} catch (OAuth2AuthenticationException e) {\n    if (OAuth2ErrorCodes.INVALID_GRANT.equals(e.getError().getErrorCode())) {\n        // restart device flow: new device authorization request\n    }\n}","preventionTips":["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"],"tags":["oauth2","device-flow","invalid-grant","spring-security"],"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"}