spring-projects/spring-security · error · OAuth2AuthenticationException
invalid_token
invalid_token
Error message
Invalid bearer token
What it means
BearerTokenAuthenticationFilter throws this when authentication succeeds but the resulting access token is DPoP-bound while the request used it as a plain Bearer token. Spring Security rejects this downgrade to prevent a token issued for DPoP (proof-of-possession) usage from being replayed without the bound key proof. The error surfaces as OAuth2AuthenticationException with code invalid_token even though the token itself may be valid.
Source
Thrown at oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java:187
this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
this.authenticationEntryPoint.commence(request, response, invalid);
return;
}
if (authenticationRequest == null) {
this.logger.trace("Did not process request since did not find bearer token");
filterChain.doFilter(request, response);
return;
}
try {
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
if (isDPoPBoundAccessToken(authenticationResult)) {
// Prevent downgraded usage of DPoP-bound access tokens,
// by rejecting a DPoP-bound access token received as a bearer token.
BearerTokenError error = BearerTokenErrors.invalidToken("Invalid bearer token");
throw new OAuth2AuthenticationException(error);
}
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
if (current != null && current.isAuthenticated() && declaresToBuilder(authenticationResult)) {
authenticationResult = authenticationResult.toBuilder().authorities((a) -> {
Set<String> newAuthorities = a.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toUnmodifiableSet());
for (GrantedAuthority currentAuthority : current.getAuthorities()) {
if (!newAuthorities.contains(currentAuthority.getAuthority())) {
a.add(currentAuthority);
}
}
}).build();
}
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authenticationResult);
this.securityContextHolderStrategy.setContext(context);
this.securityContextRepository.saveContext(context, request, response);View on GitHub (pinned to 96852e8860)
Solutions
- Update the client to send a valid DPoP proof header (DPoP) with each request and use the 'DPoP' authorization scheme
- Request a non-DPoP-bound (plain bearer) token from the authorization server for bearer-only clients
- If you must accept DPoP tokens as bearer tokens (not recommended), customize the filter/authentication logic, understanding the security implications
- Verify the authorization server client configuration (token_endpoint_auth binding) matches how the token will be used
Example fix
// before
request.header("Authorization", "Bearer " + accessToken);
// after (client with DPoP support)
String proof = dPoPSigner.generateProof(accessToken, httpMethod, url);
request.header("Authorization", "DPoP " + accessToken);
request.header("DPoP", proof); Defensive patterns
Strategy: validation
Validate before calling
if (tokenEndpointResponse.getAccessToken().getTokenType().getValue().equalsIgnoreCase("DPoP")
&& !clientSupportsDPoP) {
throw new IllegalStateException("Client received a DPoP-bound token but cannot send DPoP proofs");
} Type guard
boolean isDpopBound(OAuth2AccessToken token) {
return "DPoP".equalsIgnoreCase(token.getTokenType().getValue());
} Try / catch
try {
return chain.filter(exchange);
} catch (OAuth2AuthenticationException e) {
if ("invalid_token".equals(e.getError().getErrorCode())) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
}
return Mono.error(e);
} Prevention
- Match the token type requested at the token endpoint with the client's actual capabilities
- If the client cannot do DPoP, request plain bearer tokens (aud/grant configuration)
- Keep DPoP proof generation in one shared client interceptor so it is never skipped
- Document at the authorization server which clients are DPoP-enabled
When it happens
Trigger: A client obtains an access token bound to a DPoP key from the authorization server, then calls the resource server with 'Authorization: Bearer <token>' and no DPoP proof header. The filter detects the DPoP binding in the authenticated result (isDPoPBoundAccessToken) and rejects the request.
Common situations: Clients that previously used opaque/JWT bearer tokens switching to DPoP-issued tokens without updating their HTTP client; SDKs that don't support DPoP proof generation; token endpoint misconfiguration returning DPoP-bound tokens to bearer-only clients.
Related errors
- invalid_dpop_proof
- OAuth2Error read from WWW-Authenticate header or error respo
- method is invalid
- targetUri must be a valid URL
- targetUri cannot contain query or fragment parts
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/b859bb8e07f03ede.
Report an issue: GitHub.