What Is DPoP? Sender-Constrained OAuth Tokens Explained
DPoP binds OAuth access tokens to a client-held key, so a stolen token is useless without the private key. How proofs work and when to use DPoP.
DPoP, short for Demonstrating Proof of Possession, is an extension to OAuth 2.0 that binds an access token to a cryptographic key held by the client. Every time the client uses the token, it must also send a fresh, signed proof that it holds the matching private key. A token stolen from logs, a proxy, or a compromised browser extension is useless on its own, because the attacker can’t produce valid proofs. DPoP turns bearer tokens into sender-constrained tokens.
The problem with bearer tokens
Most OAuth access tokens are bearer tokens: whoever presents the token gets access, no questions asked. That’s convenient, and it’s the model behind the Authorization: Bearer ... header you see in nearly every API. It’s also the core weakness. As our explainer on JWTs puts it, a bearer token is like cash — possession is the only credential.
Tokens leak in ordinary ways: verbose server logs, misconfigured analytics, browser storage reachable by injected scripts, intermediaries that terminate TLS. Short expiry limits the damage window, but within that window a leaked token can be replayed freely. That’s a classic replay attack, and it’s the gap DPoP is designed to close.
How DPoP works
DPoP adds one new artifact: the DPoP proof, a small JWT the client signs with its private key and sends in a DPoP HTTP header. The flow has three parts.
1. The client generates a key pair. Typically an asymmetric key such as an elliptic-curve P-256 key. In browsers, the Web Crypto API can create it as non-extractable, so JavaScript can use it for signing but can never read the raw private key.
2. The client requests a token with a proof. When calling the authorization server’s token endpoint, the client includes a DPoP proof. The server verifies the proof, then issues an access token bound to that public key. In a JWT access token, the binding appears as a confirmation claim, cnf, containing jkt — the SHA-256 thumbprint of the client’s public key. The token response reports token_type as DPoP instead of Bearer.
3. The client calls APIs with token plus proof. Each request carries two headers:
GET /api/orders HTTP/1.1
Host: api.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIs...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2Iiwiandr...
The resource server checks that the proof is valid and that the key which signed it matches the thumbprint bound into the access token. No match, no access.
What’s inside a DPoP proof
The proof’s header declares typ: "dpop+jwt", the signing algorithm, and the client’s public key as a jwk. The payload is deliberately narrow:
| Claim | Meaning |
|---|---|
jti | A unique ID for this proof, so the server can reject reuse |
htm | The HTTP method of the request, such as POST |
htu | The target URI, without query string or fragment |
iat | When the proof was created |
ath | Hash of the access token (included when calling a resource server) |
nonce | A server-supplied value, when the server requires one |
Together these pin each proof to one request: one method, one URL, one moment, one token. You can paste a proof into our JWT decoder to see the structure — it’s an ordinary signed JWT, just with a very specific job.
What the server has to verify
A resource server accepting DPoP should check, on every request:
- The proof is a well-formed JWT with
typset todpop+jwtand an asymmetric algorithm it allows. - The signature verifies against the
jwkembedded in the proof header. htmandhtumatch the actual request.iatis recent, within a tight tolerance.jtihasn’t been seen before within that window — this requires a short-lived replay cache.athmatches the hash of the presented access token.- The thumbprint of the proof’s key equals the
cnf.jktvalue bound to the token.
Servers can also issue a DPoP-Nonce response header and require clients to echo it in the next proof. That limits how far in advance a proof can be generated, which matters if an attacker briefly gains the ability to sign.
DPoP vs mTLS-bound tokens
DPoP isn’t the only way to sender-constrain tokens. The older approach binds tokens to a client TLS certificate, as described in our mTLS explainer.
| DPoP | mTLS-bound tokens | |
|---|---|---|
| Binding layer | Application (HTTP headers) | Transport (TLS handshake) |
| Works in browsers / SPAs | Yes, via Web Crypto | Poorly; client certs are awkward in browsers |
| Survives TLS-terminating proxies | Yes | Needs certificate forwarding |
| Per-request proof | Yes, a new signed JWT each call | No; the TLS session is the proof |
| Operational overhead | Replay cache, nonce handling | Certificate issuance and rotation |
| Typical fit | Public clients, mobile apps, SPAs | Server-to-server, regulated APIs |
The short version: mTLS is strong and efficient where you control both endpoints and the network path. DPoP is the practical option for public clients, especially single-page apps and mobile apps that can’t hold a client secret and already rely on PKCE.
What DPoP does not protect against
DPoP raises the bar for token theft, but it’s not a cure-all.
- Code running in your client can still sign. If an attacker achieves cross-site scripting, a non-extractable key stops them from exfiltrating it, but their script can still ask the browser to sign proofs while the page is open. Server nonces and short lifetimes shrink this window; they don’t eliminate it.
- It doesn’t replace TLS. DPoP proofs aren’t encrypted. Everything should still travel over HTTPS.
- It adds state on the server. The
jtireplay check needs a shared cache across instances, which slightly undercuts the “stateless” appeal of JWT access tokens. - Refresh tokens matter too. For public clients, the authorization server should bind refresh tokens to the same key, or a stolen refresh token becomes the new weak link.
DPoP is one layer of defense against session hijacking, alongside content security policies, short token lifetimes, and careful storage choices.
When to adopt it
DPoP is worth considering when your API holds sensitive data, your clients are public (browser or mobile), and you can’t practically deploy client certificates. Most mature identity providers and OAuth libraries support it, so in practice adoption usually means enabling it on the authorization server, generating a key in the client, and adding proof validation middleware on resource servers. As with any auth plumbing, prefer a well-tested library over hand-rolling proof validation.
The takeaway
DPoP converts OAuth bearer tokens into sender-constrained tokens by binding them to a client-held key and requiring a fresh signed proof on every request. Each proof is tied to one method, URL, time, and token, so a leaked access token can’t be replayed elsewhere. It’s the browser-friendly alternative to mTLS-bound tokens — not a substitute for HTTPS or XSS defenses, but a meaningful upgrade over plain bearer tokens for public clients.
Keep reading
Chisato · · 5 min read What Is an Open Redirect? Risks and How to Prevent It
An open redirect lets attackers use your domain to send users to any URL. How open redirect vulnerabilities work, why they matter, and how to fix them.
Chisato · · 4 min read What Is a Man-in-the-Browser Attack?
A man-in-the-browser attack uses malware inside the browser itself to alter what a user sees and submits, bypassing HTTPS and session protections entirely.
The Lycoris Team · · 5 min read CSRF vs. XSS: What's the Difference?
CSRF forges a request using a victim's login session; XSS runs the attacker's own code inside the victim's browser. Different mechanisms, different fixes.