spring-projects/spring-security · error · OAuth2AuthenticationException

invalid_request

invalid_request

Error message

Found multiple Authorization headers.

What it means

DPoPAuthenticationConverter throws invalid_request when the request contains more than one Authorization header. HTTP semantics treat duplicated headers for single-value fields as ambiguous, so the converter refuses to guess which credential to use. This happens before any DPoP-specific parsing.

Source

Thrown at oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/DPoPAuthenticationConverter.java:63

 * @since 7.1
 * @see AuthenticationConverter
 * @see DPoPAuthenticationToken
 */
public final class DPoPAuthenticationConverter implements AuthenticationConverter {

	private static final Pattern AUTHORIZATION_PATTERN = Pattern.compile("^DPoP (?<token>[a-zA-Z0-9-._~+/]+=*)$",
			Pattern.CASE_INSENSITIVE);

	@Override
	public @Nullable Authentication convert(HttpServletRequest request) {
		List<String> authorizationList = Collections.list(request.getHeaders(HttpHeaders.AUTHORIZATION));
		if (CollectionUtils.isEmpty(authorizationList)) {
			return null;
		}
		if (authorizationList.size() != 1) {
			OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,
					"Found multiple Authorization headers.", null);
			throw new OAuth2AuthenticationException(error);
		}
		String authorization = authorizationList.get(0);
		if (!StringUtils.startsWithIgnoreCase(authorization, OAuth2AccessToken.TokenType.DPOP.getValue())) {
			return null;
		}
		Matcher matcher = AUTHORIZATION_PATTERN.matcher(authorization);
		if (!matcher.matches()) {
			OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "DPoP access token is malformed.",
					null);
			throw new OAuth2AuthenticationException(error);
		}
		String accessToken = matcher.group("token");
		List<String> dPoPProofList = Collections.list(request.getHeaders(OAuth2AccessToken.TokenType.DPOP.getValue()));
		if (CollectionUtils.isEmpty(dPoPProofList) || dPoPProofList.size() != 1) {
			OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, "DPoP proof is missing or invalid.",
					null);
			throw new OAuth2AuthenticationException(error);
		}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Ensure the client sets exactly one Authorization header per request (use set/replace semantics, not append)
  2. Check reverse-proxy/gateway configuration for injected or forwarded Authorization headers
  3. Log or inspect incoming headers (request.getHeaders("Authorization")) to find which component duplicates the header
  4. If a proxy legitimately adds credentials, strip the original header before forwarding

Example fix

// before
request.addHeader("Authorization", "DPoP " + token); // may duplicate
// after
request.setHeader("Authorization", "DPoP " + token);
Defensive patterns

Strategy: validation

Validate before calling

Enumeration<String> authHeaders = request.getHeaders("Authorization");
int count = 0;
while (authHeaders.hasMoreElements()) { authHeaders.nextElement(); count++; }
if (count > 1) {
    throw new IllegalStateException("Request must have exactly one Authorization header");
}

Try / catch

try {
    Authentication auth = converter.convert(request);
} catch (OAuth2AuthenticationException e) {
    if (OAuth2ErrorCodes.INVALID_REQUEST.equals(e.getError().getErrorCode())) {
        response.sendError(400, "Duplicated Authorization header");
    }
}

Prevention

When it happens

Trigger: A request arrives with two or more Authorization headers (e.g. one 'DPoP ...' and one 'Bearer ...', or duplicates added by a proxy/client library that appends rather than sets the header). CollectionUtils.isEmpty passes but authorizationList.size() != 1.

Common situations: Client code calling addHeader instead of setHeader; intermediary proxies or gateways injecting their own Authorization header; frameworks merging configured default headers with per-request headers.

Related errors


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