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

  1. Ensure the authorization code is exchanged exactly once: perform the token exchange in a single place (e.g. BFF) and guard against duplicate execution.
  2. Check client code for automatic retries of the token request; disable or de-duplicate retries and make the exchange idempotent at the application level.
  3. Get a fresh authorization code by redirecting the user through the authorization endpoint again; a consumed code is never reusable.
  4. 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

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


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