{"record":{"id":"cf7238fcfb97f971","repo":"spring-projects/spring-security","slug":"invalid-request-cf7238","errorCode":"invalid_request","errorMessage":"Found multiple Authorization headers.","messagePattern":"Found multiple Authorization headers\\.","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":401,"severity":"error","filePath":"oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/DPoPAuthenticationConverter.java","lineNumber":63,"sourceCode":" * @since 7.1\n * @see AuthenticationConverter\n * @see DPoPAuthenticationToken\n */\npublic final class DPoPAuthenticationConverter implements AuthenticationConverter {\n\n\tprivate static final Pattern AUTHORIZATION_PATTERN = Pattern.compile(\"^DPoP (?<token>[a-zA-Z0-9-._~+/]+=*)$\",\n\t\t\tPattern.CASE_INSENSITIVE);\n\n\t@Override\n\tpublic @Nullable Authentication convert(HttpServletRequest request) {\n\t\tList<String> authorizationList = Collections.list(request.getHeaders(HttpHeaders.AUTHORIZATION));\n\t\tif (CollectionUtils.isEmpty(authorizationList)) {\n\t\t\treturn null;\n\t\t}\n\t\tif (authorizationList.size() != 1) {\n\t\t\tOAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST,\n\t\t\t\t\t\"Found multiple Authorization headers.\", null);\n\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t}\n\t\tString authorization = authorizationList.get(0);\n\t\tif (!StringUtils.startsWithIgnoreCase(authorization, OAuth2AccessToken.TokenType.DPOP.getValue())) {\n\t\t\treturn null;\n\t\t}\n\t\tMatcher matcher = AUTHORIZATION_PATTERN.matcher(authorization);\n\t\tif (!matcher.matches()) {\n\t\t\tOAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, \"DPoP access token is malformed.\",\n\t\t\t\t\tnull);\n\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t}\n\t\tString accessToken = matcher.group(\"token\");\n\t\tList<String> dPoPProofList = Collections.list(request.getHeaders(OAuth2AccessToken.TokenType.DPOP.getValue()));\n\t\tif (CollectionUtils.isEmpty(dPoPProofList) || dPoPProofList.size() != 1) {\n\t\t\tOAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST, \"DPoP proof is missing or invalid.\",\n\t\t\t\t\tnull);\n\t\t\tthrow new OAuth2AuthenticationException(error);\n\t\t}","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/DPoPAuthenticationConverter.java#L45-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrequest.addHeader(\"Authorization\", \"DPoP \" + token); // may duplicate\n// after\nrequest.setHeader(\"Authorization\", \"DPoP \" + token);","handlingStrategy":"validation","validationCode":"Enumeration<String> authHeaders = request.getHeaders(\"Authorization\");\nint count = 0;\nwhile (authHeaders.hasMoreElements()) { authHeaders.nextElement(); count++; }\nif (count > 1) {\n    throw new IllegalStateException(\"Request must have exactly one Authorization header\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    Authentication auth = converter.convert(request);\n} catch (OAuth2AuthenticationException e) {\n    if (OAuth2ErrorCodes.INVALID_REQUEST.equals(e.getError().getErrorCode())) {\n        response.sendError(400, \"Duplicated Authorization header\");\n    }\n}","preventionTips":["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"],"tags":["oauth2","dpop","http-header","spring-security"],"backgroundTag":"duplicate-authorization-header","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}