spring-projects/spring-security · error
Invalidated authorization code used by registered client '%s
Error message
Invalidated authorization code used by registered client '%s'
What it means
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.
Source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java:156
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
.getToken(OAuth2AuthorizationCode.class);
Assert.notNull(authorizationCode, "authorizationCode cannot be null");
OAuth2AuthorizationRequest authorizationRequest = authorization
.getAttribute(OAuth2AuthorizationRequest.class.getName());
Assert.notNull(authorizationRequest, "authorizationRequest cannot be null");
if (!registeredClient.getClientId().equals(authorizationRequest.getClientId())) {
if (!authorizationCode.isInvalidated()) {
// Invalidate the authorization code given that a different client is
// attempting to use it
authorization = OAuth2Authorization.from(authorization)
.invalidate(authorizationCode.getToken())
.build();
this.authorizationService.save(authorization);
if (this.logger.isWarnEnabled()) {
this.logger.warn(LogMessage.format("Invalidated authorization code used by registered client '%s'",
registeredClient.getId()));
}
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
if (StringUtils.hasText(authorizationRequest.getRedirectUri())
&& !authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format(
"Invalid request: redirect_uri does not match" + " for registered client '%s'",
registeredClient.getId()));
}
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
}
if (!authorizationCode.isActive()) {
if (authorizationCode.isInvalidated()) {View on GitHub (pinned to 96852e8860)
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.
Example fix
// before (SPA fires exchange twice on mount)
useEffect(() => { exchangeCode(code); }, [code]);
// after
const done = useRef(false);
useEffect(() => {
if (!done.current) { done.current = true; exchangeCode(code); }
}, [code]); Defensive patterns
Strategy: try-catch
Validate before calling
// client-side guard before calling the token endpoint
if (codeUsed.getIfPresent(code) != null) {
throw new IllegalStateException("authorization code already exchanged");
}
codeUsed.put(code, true); Try / catch
try {
TokenResponse r = exchangeCode(code);
} catch (OAuth2AuthenticationException e) {
if ("invalid_grant".equals(e.getError().getErrorCode())) {
// code consumed or revoked: redirect to authorization endpoint for a new code
}
} Prevention
- 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'
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- OAuth2Error from upstream authorization exception (dynamic)
- OAuth2Error from authorization response error (dynamic)
- OAuth2Error from upstream authorization exception (dynamic)
- OAuth2Error from upstream authorization exception (dynamic)
- Invalidated authorization token(s) previously issued to regi
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/dd70dd4be4181b98.
Report an issue: GitHub.