Modern API security has evolved far beyond simple bearer tokens. While OAuth 2.0 bearer access tokens are widely used, they suffer from a fundamental weakness: whoever possesses the token can use it. If an attacker intercepts the token—via a compromised network, browser extension, proxy, or log leak—the token can be replayed until it expires. Demonstrating Proof-Of-Possession (DPoP) was introduced to address this problem by cryptographically binding an access token to a specific client-held key.

In this article, we explore how DPoP works, why it is effective, and how it can be implemented in a Spring Boot ecosystem. We will walk through the cryptographic concepts, OAuth flow modifications, and practical Spring Security–based code examples. By the end, you will understand how DPoP ensures that intercepted access tokens cannot be misused and how to apply this mechanism in real-world Spring Boot applications.

Understanding The Bearer Token Problem

OAuth 2.0 bearer tokens operate under a simple rule: possession equals authorization. If a request contains a valid access token in the Authorization: Bearer header, the resource server accepts it. This simplicity is also the biggest security flaw.

Common attack scenarios include:

  • Token interception through unsecured logs or debugging tools
  • Man-in-the-middle attacks in partially trusted networks
  • Browser-based attacks such as XSS extracting tokens from JavaScript memory
  • Reverse proxy or gateway misconfiguration exposing headers

In all of these cases, the attacker does not need to know who the client is. The attacker only needs the token. DPoP changes this by requiring proof that the sender of the token also controls a private cryptographic key.

What Is Demonstrating Proof-Of-Possession (DPoP)?

DPoP is an OAuth 2.0 extension that binds an access token to a client-generated asymmetric key pair. Instead of relying on token possession alone, the client must demonstrate possession of the private key for every request.

At a high level:

  1. The client generates a public/private key pair.
  2. The client sends the public key to the authorization server during token acquisition.
  3. The authorization server embeds a confirmation (cnf) claim in the access token.
  4. For each API request, the client sends a signed DPoP proof JWT.
  5. The resource server validates both the access token and the DPoP proof.

If the token is intercepted but the attacker does not have the private key, the token becomes useless.

Cryptographic Binding: How DPoP Actually Works

The core security property of DPoP comes from asymmetric cryptography. The private key never leaves the client, while the public key is used by servers to verify signatures.

Each request includes a DPoP HTTP header containing a JWT. This JWT is signed using the client’s private key and includes claims such as:

  • htu (HTTP URI): the target endpoint
  • htm (HTTP method): GET, POST, etc.
  • iat (issued at): timestamp to prevent replay
  • jti (JWT ID): unique identifier

The access token includes a cnf claim referencing the public key thumbprint. The resource server ensures the DPoP JWT signature matches the public key bound to the access token.

This creates a three-way binding:

  • The request
  • The access token
  • The client-held private key

DPoP In An OAuth 2.0 Flow

DPoP modifies the standard OAuth 2.0 flow slightly:

  1. Client Key Generation: The client generates a key pair locally.
  2. Token Request: The client sends a DPoP proof JWT along with the token request.
  3. Access Token Issuance: The authorization server binds the public key to the token.
  4. API Calls: Each API call includes both the access token and a DPoP proof.

This flow is compatible with existing OAuth grant types, including authorization code and client credentials.

Generating A DPoP Key Pair In Java

In a Spring Boot–based client application, key generation typically occurs at startup.

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator.initialize(256);
KeyPair keyPair = keyPairGenerator.generateKeyPair();

PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();

Elliptic Curve (EC) keys are commonly used due to their smaller size and strong security properties.

Creating A DPoP Proof JWT

Each outgoing request requires a freshly signed DPoP JWT.

Instant now = Instant.now();

JWTClaimsSet claims = new JWTClaimsSet.Builder()
        .claim("htu", "https://api.example.com/resource")
        .claim("htm", "GET")
        .issueTime(Date.from(now))
        .jwtID(UUID.randomUUID().toString())
        .build();

SignedJWT dpopJwt = new SignedJWT(
        new JWSHeader.Builder(JWSAlgorithm.ES256)
                .type(new JOSEObjectType("dpop+jwt"))
                .build(),
        claims
);

dpopJwt.sign(new ECDSASigner(privateKey));

String dpopHeader = dpopJwt.serialize();

This DPoP proof is sent in the DPoP HTTP header.

Requesting An Access Token With DPoP

When requesting an access token, the client includes a DPoP proof that demonstrates possession of the private key.

HttpHeaders headers = new HttpHeaders();
headers.set("DPoP", dpopHeader);
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "client_credentials");

HttpEntity<?> request = new HttpEntity<>(body, headers);

The authorization server extracts the public key from the DPoP proof and binds it to the issued access token.

Binding The Access Token To The Client Key

The issued access token contains a confirmation claim:

"cnf": {
  "jkt": "base64url-thumbprint"
}

This thumbprint uniquely identifies the public key. The resource server later compares this thumbprint with the key used to sign the DPoP proof.

Validating DPoP In A Spring Boot Resource Server

On the resource server side, Spring Security can be extended with a custom filter.

@Component
public class DPoPValidationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String dpopHeader = request.getHeader("DPoP");
        String authHeader = request.getHeader("Authorization");

        // Validate access token and extract cnf claim
        // Validate DPoP JWT signature
        // Match public key thumbprint

        filterChain.doFilter(request, response);
    }
}

This filter ensures that both the access token and DPoP proof are cryptographically linked.

Preventing Token Replay Attacks

DPoP includes built-in replay protection:

  • Short-lived JWTs
  • Unique jti values
  • Timestamp validation

The resource server can maintain a cache of recent jti values to detect replay attempts.

Handling Proxies And Load Balancers

DPoP is designed to be compatible with modern infrastructure. Since the proof is bound to the HTTP method and URI, reverse proxies must forward the original request values accurately. Careful normalization of URIs is critical to avoid false validation failures.

Performance Considerations

Although DPoP introduces cryptographic operations per request, the overhead is generally minimal with modern hardware. Caching public key thumbprints and using efficient signature algorithms keeps performance impact low.

Security Benefits Over Traditional Bearer Tokens

DPoP provides several key advantages:

  • Eliminates token replay by unauthorized parties
  • Limits damage from token leakage
  • Strengthens zero-trust architectures
  • Works without mutual TLS complexity

Unlike network-bound approaches, DPoP binds identity at the application layer.

Common Implementation Pitfalls

Developers should be aware of common mistakes:

  • Reusing DPoP proofs across requests
  • Failing to validate htu and htm
  • Allowing excessive clock skew
  • Ignoring replay detection

Proper validation is essential to preserve DPoP’s security guarantees.

Conclusion

Demonstrating Proof-Of-Possession fundamentally changes how access tokens are protected in OAuth 2.0–based systems. Instead of relying on possession alone, DPoP cryptographically binds access tokens to a client-held private key, ensuring that only the legitimate client can use the token. Even if an attacker intercepts the token, the absence of the private key renders it useless.

Within a Spring Boot implementation, DPoP integrates naturally with existing OAuth and Spring Security infrastructures. By generating client-side key pairs, embedding public key thumbprints in access tokens, and validating signed DPoP proofs on every request, developers can significantly raise the security bar without introducing excessive complexity. The approach scales well, works across distributed systems, and aligns with zero-trust security principles.

Most importantly, DPoP addresses one of the longest-standing weaknesses in bearer token–based authentication. It transforms access tokens from transferable secrets into cryptographically bound credentials. For organizations building high-security APIs or operating in hostile environments, DPoP is not merely an enhancement—it is a necessary evolution in modern API security architecture.