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
- Ensure the client sets exactly one Authorization header per request (use set/replace semantics, not append)
- Check reverse-proxy/gateway configuration for injected or forwarded Authorization headers
- Log or inspect incoming headers (request.getHeaders("Authorization")) to find which component duplicates the header
- 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
- Use setHeader not addHeader when constructing outbound requests
- Audit gateway/proxy configurations for Authorization header injection
- Test behind your full proxy chain, not just locally
- Strip client-supplied Authorization headers where a gateway re-signs requests
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.