{"record":{"id":"6de607b7d2bc2513","repo":"spring-projects/spring-security","slug":"invalid-token-6de607","errorCode":"invalid_token","errorMessage":"DPoP access token is malformed.","messagePattern":"DPoP access token is malformed\\.","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":73,"sourceCode":"\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}\n\t\tString dPoPProof = dPoPProofList.get(0);\n\t\treturn new DPoPAuthenticationToken(accessToken, dPoPProof, request.getMethod(),\n\t\t\t\trequest.getRequestURL().toString());\n\t}\n\n}\n","sourceCodeStart":55,"sourceCodeEnd":88,"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#L55-L88","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrequest.setHeader(\"Authorization\", \"DPoP \" + accessToken + \" extra\");\n// after\nrequest.setHeader(\"Authorization\", \"DPoP \" + accessToken.trim());","handlingStrategy":"validation","validationCode":"String auth = request.getHeader(\"Authorization\");\nif (auth != null && auth.regionMatches(true, 0, \"DPoP \", 0, 5)) {\n    String token = auth.substring(5).trim();\n    if (token.isEmpty() || token.matches(\".*\\\\s.*\")) {\n        throw new IllegalArgumentException(\"Malformed DPoP access token\");\n    }\n}","typeGuard":"boolean isValidDpopHeader(String header) {\n    return header != null && header.matches(\"(?i)^DPoP [!-~]+$\");\n}","tryCatchPattern":"try {\n    Authentication auth = converter.convert(request);\n} catch (OAuth2AuthenticationException e) {\n    if (OAuth2ErrorCodes.INVALID_TOKEN.equals(e.getError().getErrorCode())) {\n        response.setStatus(401);\n        response.setHeader(\"WWW-Authenticate\", \"DPoP error=\\\"invalid_token\\\"\");\n    }\n}","preventionTips":["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"],"tags":["oauth2","dpop","http-header","spring-security"],"backgroundTag":"invalid-token-format","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"}