{"record":{"id":"dd70dd4be4181b98","repo":"spring-projects/spring-security","slug":"invalidated-authorization-code-used-by-registered","errorCode":null,"errorMessage":"Invalidated authorization code used by registered client '%s'","messagePattern":"Invalidated authorization 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/OAuth2AuthorizationCodeAuthenticationProvider.java","lineNumber":156,"sourceCode":"\n\t\tOAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization\n\t\t\t.getToken(OAuth2AuthorizationCode.class);\n\t\tAssert.notNull(authorizationCode, \"authorizationCode cannot be null\");\n\n\t\tOAuth2AuthorizationRequest authorizationRequest = authorization\n\t\t\t.getAttribute(OAuth2AuthorizationRequest.class.getName());\n\t\tAssert.notNull(authorizationRequest, \"authorizationRequest cannot be null\");\n\n\t\tif (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) {\n\t\t\tif (!authorizationCode.isInvalidated()) {\n\t\t\t\t// Invalidate the authorization code given that a different client is\n\t\t\t\t// attempting to use it\n\t\t\t\tauthorization = OAuth2Authorization.from(authorization)\n\t\t\t\t\t.invalidate(authorizationCode.getToken())\n\t\t\t\t\t.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 authorization code used by registered client '%s'\",\n\t\t\t\t\t\t\tregisteredClient.getId()));\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);\n\t\t}\n\n\t\tif (StringUtils.hasText(authorizationRequest.getRedirectUri())\n\t\t\t\t&& !authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {\n\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()) {","sourceCodeStart":138,"sourceCodeEnd":174,"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#L138-L174","documentation":"This is a warning log emitted by OAuth2AuthorizationCodeAuthenticationProvider when an authorization code is presented a second time at the token endpoint. Per RFC 6749 section 4.1.2, if an authorization code is already used, the authorization server MUST deny the request and SHOULD revoke all tokens previously issued based on that code. The provider invalidates the code, saves the updated authorization, then throws OAuth2AuthenticationException with error code INVALID_GRANT.","triggerScenarios":"A client POSTs to the token endpoint with the same authorization code parameter twice (e.g. duplicate token request, client retry after a timeout, or replay of an intercepted code). The provider looks up the authorization, sees the code already invalidated by a previous exchange, and takes this path.","commonSituations":"Developers see this when a frontend fires the token exchange twice (double React effect/strict mode double-render), a gateway retries a timed-out POST, or a load balancer resends the request. Also appears when a shared token request is executed by both a BFF and the SPA.","solutions":["Ensure the authorization code is exchanged exactly once: perform the token exchange in a single place (e.g. BFF) and guard against duplicate execution.","Check client code for automatic retries of the token request; disable or de-duplicate retries and make the exchange idempotent at the application level.","Get a fresh authorization code by redirecting the user through the authorization endpoint again; a consumed code is never reusable.","Issue a refresh token instead of re-running the code exchange for new tokens."],"exampleFix":"// before (SPA fires exchange twice on mount)\nuseEffect(() => { exchangeCode(code); }, [code]);\n// after\nconst done = useRef(false);\nuseEffect(() => {\n  if (!done.current) { done.current = true; exchangeCode(code); }\n}, [code]);","handlingStrategy":"try-catch","validationCode":"// client-side guard before calling the token endpoint\nif (codeUsed.getIfPresent(code) != null) {\n  throw new IllegalStateException(\"authorization code already exchanged\");\n}\ncodeUsed.put(code, true);","typeGuard":null,"tryCatchPattern":"try {\n  TokenResponse r = exchangeCode(code);\n} catch (OAuth2AuthenticationException e) {\n  if (\"invalid_grant\".equals(e.getError().getErrorCode())) {\n    // code consumed or revoked: redirect to authorization endpoint for a new code\n  }\n}","preventionTips":["Perform the code exchange in exactly one code path (BFF) and de-duplicate double mounts/retries","Never retry a failed token request with the same code without checking server state","Use state+PKCE to pair codes with a single browser session","Treat INVALID_GRANT as 'start the flow over', not 'retry'"],"tags":["oauth2","authorization-code","invalid-grant","token-exchange","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-14T11:17:12.474Z"}