spring-projects/spring-security · error · OAuth2AuthenticationException
invalid_token
invalid_token
Error message
DPoP access token is malformed.
What it means
DPoPAuthenticationConverter throws invalid_token when the Authorization header uses the DPoP scheme but its value does not match the expected single-token pattern (e.g. it contains whitespace or is empty after the scheme). Like the bearer equivalent, this is a structural parse failure of the access token before validation.
Source
Thrown at oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/DPoPAuthenticationConverter.java:73
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);
}
String dPoPProof = dPoPProofList.get(0);
return new DPoPAuthenticationToken(accessToken, dPoPProof, request.getMethod(),
request.getRequestURL().toString());
}
}
View on GitHub (pinned to 96852e8860)
Solutions
- Send exactly 'Authorization: DPoP <single-token>' with no internal whitespace
- Trim the token and any surrounding whitespace before setting the header
- Regenerate the token if it contains characters outside the allowed token charset
- Log the raw header value to confirm what is actually being sent
Example fix
// before
request.setHeader("Authorization", "DPoP " + accessToken + " extra");
// after
request.setHeader("Authorization", "DPoP " + accessToken.trim()); Defensive patterns
Strategy: validation
Validate before calling
String auth = request.getHeader("Authorization");
if (auth != null && auth.regionMatches(true, 0, "DPoP ", 0, 5)) {
String token = auth.substring(5).trim();
if (token.isEmpty() || token.matches(".*\\s.*")) {
throw new IllegalArgumentException("Malformed DPoP access token");
}
} Type guard
boolean isValidDpopHeader(String header) {
return header != null && header.matches("(?i)^DPoP [!-~]+$");
} Try / catch
try {
Authentication auth = converter.convert(request);
} catch (OAuth2AuthenticationException e) {
if (OAuth2ErrorCodes.INVALID_TOKEN.equals(e.getError().getErrorCode())) {
response.setStatus(401);
response.setHeader("WWW-Authenticate", "DPoP error=\"invalid_token\"");
}
} Prevention
- Build the DPoP header from the token type constant, not hand-typed strings
- Trim tokens pulled from environment variables or config files
- Validate header construction in unit tests before shipping clients
When it happens
Trigger: Sending 'Authorization: DPoP' with a malformed value: token containing spaces, an empty token, or extra characters after the token. The prefix check (startsWithIgnoreCase "DPoP") passes but AUTHORIZATION_PATTERN.matches() fails.
Common situations: Tokens pasted with trailing whitespace/newlines; clients hand-building the header with string concatenation errors; migration from Bearer to DPoP scheme where the header construction wasn't updated correctly.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/6de607b7d2bc2513.
Report an issue: GitHub.