spring-projects/spring-security · error · OAuth2AuthenticationException

OAuth2Error from upstream authorization exception (dynamic)

Error message

OAuth2Error from upstream authorization exception (dynamic)

What it means

OidcAuthorizationCodeAuthenticationProvider.getResponse() calls the configured accessTokenResponseClient to exchange the code. Any OAuth2AuthorizationException raised there (network error, invalid_token_response, token endpoint rejection) is converted into an OAuth2AuthenticationException whose message is the upstream OAuth2Error's toString. The real cause is in the wrapped error/cause.

Source

Thrown at oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java:183

		Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
			.mapAuthorities(oidcUser.getAuthorities());
		OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
				authorizationCodeAuthentication.getClientRegistration(),
				authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,
				accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
		authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
		return authenticationResult;
	}

	private OAuth2AccessTokenResponse getResponse(OAuth2LoginAuthenticationToken authorizationCodeAuthentication) {
		try {
			return this.accessTokenResponseClient.getTokenResponse(
					new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
							authorizationCodeAuthentication.getAuthorizationExchange()));
		}
		catch (OAuth2AuthorizationException ex) {
			OAuth2Error oauth2Error = ex.getError();
			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
		}
	}

	private void validateNonce(OAuth2AuthorizationRequest authorizationRequest, OidcIdToken idToken) {
		String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
		if (requestNonce == null) {
			return;
		}
		String nonceHash = getNonceHash(requestNonce);
		String nonceHashClaim = idToken.getNonce();
		if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
			OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
		}
	}

	private String getNonceHash(String requestNonce) {
		try {

View on GitHub (pinned to 96852e8860)

Solutions

  1. Inspect ex.getError() (code + description) — typical values are invalid_grant, invalid_client, invalid_token_response.
  2. For invalid_grant: prevent code reuse/replays (don't re-post the login callback) and check clock skew between app and OP.
  3. For invalid_client: fix client credentials and clientAuthenticationMethod.
  4. For invalid_token_response: verify the token endpoint returns proper JSON (see empty/malformed response fixes).

Example fix

// before: user refreshes callback URL -> invalid_grant surfaces as 500
// after: catch and redirect to a re-login flow
catch (OAuth2AuthenticationException ex) {
    if ("invalid_grant".equals(ex.getError().getErrorCode())) {
        return "redirect:/oauth2/authorization/" + registrationId;
    }
    throw ex;
}
Defensive patterns

Strategy: try-catch

Try / catch

catch (OAuth2AuthenticationException ex) {
    String code = ex.getError().getErrorCode();
    if ("invalid_grant".equals(code)) { return "redirect:/oauth2/authorization/" + registrationId; }
    if ("invalid_client".equals(code)) { throw new ConfigurationException("Check client credentials/auth method", ex); }
    throw ex;
}

Prevention

When it happens

Trigger: Thrown in getResponse() (invoked from authenticate via accessTokenResponse) when the access-token request for the OIDC authorization-code grant fails: HTTP error from the token endpoint, empty body, connection failure, or client-authentication failure.

Common situations: Wrong token URI, expired/already-redeemed authorization code (browser refresh or back-button), client authentication mismatch (basic vs post vs private_key_jwt), or the OP returning invalid_grant.

Related errors


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