{"record":{"id":"359ddc40712500b4","repo":"spring-projects/spring-security","slug":"invalidated-authorization-token-s-previously-issu","errorCode":null,"errorMessage":"Invalidated authorization token(s) previously issued to registered client '%s'","messagePattern":"Invalidated authorization token\\(s\\) previously issued to 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/OAuth2AuthorizationCodeAuthenticationProvider.java","lineNumber":183,"sourceCode":"\t\t\tif (this.logger.isDebugEnabled()) {\n\t\t\t\tthis.logger.debug(LogMessage.format(\n\t\t\t\t\t\t\"Invalid request: redirect_uri does not match\" + \" for registered client '%s'\",\n\t\t\t\t\t\tregisteredClient.getId()));\n\t\t\t}\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);\n\t\t}\n\n\t\tif (!authorizationCode.isActive()) {\n\t\t\tif (authorizationCode.isInvalidated()) {\n\t\t\t\tOAuth2Authorization.Token<? extends OAuth2Token> token = (authorization.getRefreshToken() != null)\n\t\t\t\t\t\t? authorization.getRefreshToken() : authorization.getAccessToken();\n\t\t\t\tif (token != null) {\n\t\t\t\t\t// Invalidate the access (and refresh) token as the client is\n\t\t\t\t\t// attempting to use the authorization code more than once\n\t\t\t\t\tauthorization = OAuth2Authorization.from(authorization).invalidate(token.getToken()).build();\n\t\t\t\t\tthis.authorizationService.save(authorization);\n\t\t\t\t\tif (this.logger.isWarnEnabled()) {\n\t\t\t\t\t\tthis.logger.warn(LogMessage.format(\n\t\t\t\t\t\t\t\t\"Invalidated authorization token(s) previously issued to registered client '%s'\",\n\t\t\t\t\t\t\t\tregisteredClient.getId()));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);\n\t\t}\n\n\t\t// Verify the DPoP Proof (if available)\n\t\tJwt dPoPProof = DPoPProofVerifier.verifyIfAvailable(authorizationCodeAuthentication);\n\n\t\tif (this.logger.isTraceEnabled()) {\n\t\t\tthis.logger.trace(\"Validated token request parameters\");\n\t\t}\n\n\t\tAuthentication principal = authorization.getAttribute(Principal.class.getName());\n\t\tAssert.notNull(principal, \"principal cannot be null\");\n","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java#L165-L201","documentation":"This warning is emitted by OAuth2AuthorizationCodeAuthenticationProvider after it detects reuse of an authorization code, while it revokes the access token (and any associated refresh token) previously issued from that code, per RFC 6749 section 4.1.2. The request is then rejected with INVALID_GRANT. It is the companion of code-reuse detection: the log confirms tokens were invalidated to contain the replay.","triggerScenarios":"Same as code reuse: a second token-endpoint request carrying an already-consumed authorization code. The provider iterates the tokens in the stored OAuth2Authorization, invalidates each one, saves it, and logs this message before throwing INVALID_GRANT.","commonSituations":"Happens after any successful code exchange that is replayed: double form submissions, retried HTTP requests, or an attacker replaying a captured code (this log is often the first evidence of such replay). Developers debugging 'invalid_grant' responses see this in server logs.","solutions":["Fix the client so the token exchange runs only once (de-duplicate effects, retries, resubmits).","If tokens are unexpectedly gone, obtain a new authorization code via a fresh authorization redirect and start the flow over.","Treat repeated occurrences as a security signal: investigate whether a code is being intercepted/replayed (use PKCE and https to mitigate).","Use refresh tokens for subsequent tokens rather than repeating the code exchange."],"exampleFix":"// before: retry loop replays code exchange\nwhile (!success) { success = exchangeToken(code); }\n// after: single attempt, fall back to refresh token\nif (!exchangeToken(code)) {\n  throw new Error('code already used, re-authenticate or use refresh token');\n}","handlingStrategy":"try-catch","validationCode":"// ensure this code exchange has not run before, and that any prior tokens are still needed\nif (hasIssuedTokensFor(code) && !wantsReissue) {\n  return cachedTokensFor(code);\n}","typeGuard":null,"tryCatchPattern":"try {\n  TokenResponse r = exchangeCode(code);\n} catch (OAuth2AuthenticationException e) {\n  if (\"invalid_grant\".equals(e.getError().getErrorCode())) {\n    // tokens based on this code were revoked: require fresh authorization\n    reauthenticate();\n  }\n}","preventionTips":["If this warning appears, expect the user's access/refresh tokens to be dead","Disable aggressive HTTP retries on the token endpoint","Use PKCE to limit replay value of intercepted codes","On invalid_grant, drop stored tokens locally before re-authenticating"],"tags":["oauth2","token-revocation","invalid-grant","replay","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"}