DPoP (RFC 9449) Implementation Guide: Stopping Stolen OAuth Token Replay
How to add proof-of-possession to OAuth clients and resource servers so a stolen access or refresh token cannot simply be replayed by whoever took it

Proactive Security for the AI Era
NodeZero continuously and autonomously pentests infrastructure, identity, cloud, and now web applications, chaining weaknesses across every domain the way real attackers do. Every finding ships with replayable proof showing exploitable business impact, not theoretical risk.
Teams that finished migrating off the Implicit Grant flow and onto Authorization Code with PKCE (see our OAuth 2.1 migration runbook if that work is still in progress) tend to assume the token security problem is solved. It is not. PKCE proves the client redeeming an authorization code is the same one that started the flow, but the access and refresh tokens that come out of that exchange are still, by default, plain bearer credentials. Anyone who gets hold of one, through a leaked log, a misconfigured proxy, a compromised CI pipeline, or a browser extension reading local storage, can use it exactly as the legitimate client would, for as long as it remains valid. DPoP, defined in RFC 9449, closes that specific gap by requiring the client to prove possession of a private key on every request, and by binding the token itself to that key's public counterpart. This is a practitioner implementation guide: what DPoP actually costs you to skip, what has to already be in place before you start, the concrete steps to add proof generation on the client and validation on the resource server, how to confirm replay protection is actually working rather than just that requests succeed, the specific ways this breaks in production, and where DPoP's protection genuinely ends.
The problem: why PKCE and short expiry alone do not stop token replay
A stolen or intercepted bearer access token is, by design, indistinguishable from a legitimate request once it reaches a resource server. The token is a string; whoever presents it in an Authorization header gets whatever access that token grants, no further proof required. PKCE does not change this, because PKCE's job ends at the authorization code exchange. Once the token is issued, PKCE has nothing further to say about how it is used. Short access token lifetimes reduce the damage window but do not eliminate it: a token with a five-minute lifetime is still fully usable by an attacker for that entire five minutes, and refresh tokens, which typically live far longer, are an even more valuable target precisely because a stolen refresh token lets an attacker mint fresh access tokens indefinitely. Real leak paths are mundane rather than exotic: access tokens end up in server access logs when a proxy or load balancer logs full request headers, in browser storage readable by an XSS payload, in CI logs when a build step prints an environment variable, or in a misdirected support ticket that pastes a raw request. In every one of those cases, a plain bearer token is a fully usable credential the moment it is copied. DPoP changes that by making the token useless without a private key that never leaves the client that generated it, so the leak of the token string alone stops being sufficient for replay.
Prerequisites before you touch a single client or resource server
DPoP is an addition on top of an already-modern OAuth deployment, not a replacement for the basics. Confirm these are in place first, since layering DPoP onto a client population still running deprecated flows just adds complexity to a foundation that needs fixing anyway.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
The implementation procedure: step by step
These steps cover client-side proof generation and key management, authorization-server DPoP-bound issuance, resource-server validation including the ath claim and jti replay tracking, and the DPoP-Nonce challenge-response flow. Run the authorization-server and resource-server changes together, since a resource server that expects DPoP proofs but receives plain bearer tokens from a client that has not yet been updated will reject every request from that client.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
Validation: confirming proofs are actually being checked, not just that requests succeed
A request succeeding tells you almost nothing about whether DPoP protection is real. Confirm each of the following directly, ideally in a staging environment where you can deliberately send malformed or replayed proofs.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
Failure cases: what actually breaks in production
Most DPoP incidents trace back to one of these four categories rather than to a flaw in the protocol itself.
Subscribe to unlock Remediation & Mitigation steps
Free subscribers unlock full IOC lists, Sigma detection rules, remediation steps, and every daily briefing.
Security tradeoffs: what DPoP does and does not buy you
DPoP meaningfully raises the bar against token theft and replay: a token by itself, without the corresponding private key, is no longer sufficient to gain access, which closes off an entire class of incidents where a leaked log, a misconfigured proxy, or a copied header was previously enough. It does not replace short token lifetimes; a shorter-lived access token still limits the damage window if a key itself is ever compromised, and DPoP should be layered on top of that discipline rather than used as a reason to loosen it. It does not protect against a fully compromised client, since malware or an attacker with code execution inside the legitimate client can generate valid proofs with the client's own key just as the real application does, so DPoP narrows the token-theft path without replacing endpoint security or monitoring for anomalous client behavior. It also adds real operational complexity: every resource server instance needs consistent access to jti replay state, every client needs a key management story appropriate to its platform, and any server relying on DPoP-Nonce needs to generate, distribute, and validate nonces correctly under load. None of that complexity is optional if the goal is real replay protection rather than a checkbox; teams evaluating whether to prioritize this work should weigh it against how much of their token population is realistically exposed to the leak paths in the problem section above, and against regulatory drivers like FAPI 2.0's mandate for sender-constrained tokens across DPoP or mTLS in open banking and financial-grade API deployments.
The bottom line
PKCE secures the authorization code exchange; it says nothing about what happens to the token afterward. DPoP (RFC 9449) closes that remaining gap by requiring a signed proof JWT, carrying htm, htu, iat, jti, and for resource server calls an ath claim, on every request, and by binding the issued token to the client's public key through the cnf.jkt claim. The mechanics are well specified and increasingly well supported, Keycloak ships native DPoP handling, Spring Security can validate DPoP-bound tokens, and FAPI 2.0 now mandates sender-constrained tokens for exactly this reason. The real work is operational: shared jti replay tracking across every resource server instance, a key storage decision appropriate to each client platform, correct htu validation behind any proxy or load balancer, and either generous iat tolerance or the DPoP-Nonce flow to handle clock skew without weakening freshness checks. Get those four right and a stolen bearer token stops being a usable credential on its own. Skip any of them and DPoP becomes a checkbox that looks like protection in a design doc without actually stopping replay in production.
Frequently asked questions
Does DPoP replace PKCE or short-lived access tokens?
No, and this is the most common misunderstanding teams run into after finishing an OAuth 2.1 migration. PKCE protects the authorization code exchange step, proving the client redeeming the code is the same one that started the flow. DPoP is a separate, later protection that binds the resulting access and refresh tokens themselves to a key the client holds, so a token intercepted or exfiltrated after issuance cannot be replayed by someone else. Short token lifetimes still matter because DPoP does not extend how long a token is valid, it changes what an attacker can do with a valid token they should not have.
What is the difference between DPoP and mTLS-bound tokens, since FAPI 2.0 allows both?
Both sender-constrain tokens, but through different mechanisms. Mutual TLS binds a token to a client certificate presented during the TLS handshake, which works well for confidential clients running in an environment that can manage certificates, such as a backend service. DPoP binds a token to an application-layer proof JWT signed with a key the client generates itself, with no dependency on the TLS stack or a certificate authority, which is why DPoP is the practical choice for single-page applications, mobile apps, and other public clients where issuing and rotating TLS client certificates is not realistic.
What happens if a client's clock is skewed relative to the authorization or resource server?
RFC 9449 allows a server to accept an iat value that is a little ahead of its own clock, on the order of seconds to minutes, specifically to absorb normal clock drift between independent systems. A client with a clock that has drifted further than that will see its proofs rejected as stale or premature, which shows up as intermittent, hard-to-reproduce authentication failures rather than a clean outright rejection. This is why RFC 9449 recommends the DPoP-Nonce mechanism for anything strict: a server-issued nonce carries the server's own timestamp, so freshness checks stop depending on the client's clock being correct at all.
If a refresh token is stolen, can it still be replayed once DPoP is enabled?
Not by the party that stole it, as long as they do not also have the private key the token was bound to. A DPoP-bound refresh token carries the same cnf.jkt thumbprint as the access tokens it was issued alongside, so a resource or authorization server checks that a fresh proof from the matching key accompanies any request to use it. An attacker holding only the token value, for example from a compromised log file or a network capture, cannot produce a valid proof without the private key, which typically never leaves the device or process that generated it.
Which identity providers and libraries currently support DPoP in production?
Keycloak ships native DPoP support with a per-client toggle and graceful fallback to plain bearer tokens for clients that have not adopted it yet. Spring Security can validate DPoP-bound access tokens on the resource-server side without custom code. Several OAuth client libraries for single-page applications and native mobile apps have added proof generation and key management as built-in features rather than something a team has to hand-roll from the RFC text. Confirm the specific version and configuration your stack ships before assuming support, since DPoP is a newer capability than PKCE and coverage still varies by product and release.
Does DPoP protect against a fully compromised client that can generate its own valid proofs?
No. DPoP protects a token in transit and at rest against being lifted and reused by a different party, but if malware or an attacker gains full code execution inside the legitimate client, that client's own private key and its ability to sign proofs are compromised along with everything else. A fully compromised client can request tokens and generate valid proofs for them just as the real application would, so DPoP is not a substitute for endpoint security, secure key storage, or monitoring for anomalous client behavior. It narrows the specific window where token theft alone is enough to succeed, it does not close every path to misuse.
Sources & references
- IETF RFC Editor - RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- IETF RFC Editor - RFC 8471: The Token Binding Protocol Version 1.0
- Curity - DPoP Overview
- Keycloak - Securing Applications with Demonstrating Proof-of-Possession (DPoP)
- WorkOS - DPoP (RFC 9449) Explained: How Sender-Constrained OAuth Tokens Make Token Theft a Non-Event
- Tyk - Demonstrating Proof of Possession (DPoP): OAuth2 Security for FAPI 2.0 and Open Banking
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
