When an OAuth authorization server issues an access token under RFC 6750, the token itself carries the full authority. The resource server's job is to check that the token is valid, unexpired, and scoped for the request. It has no way to check who is presenting it, because nothing in the design gives it that information.
For a long time this was treated as acceptable because TLS protects the token in transit and short lifetimes limit the damage. Both mitigations are real and neither addresses the case that actually happens: the token gets stolen from an endpoint rather than from the wire. Infostealer malware scrapes browser storage. A logging middleware writes the Authorization header to disk. A phishing proxy relays a real login and keeps the result. In each case TLS worked perfectly and the token still left.
What sender-constraining changes
A sender-constrained token is cryptographically bound to a key the legitimate client holds. The token still travels in the Authorization header, but the resource server will only honour it when the same request also demonstrates possession of the bound private key. Copying the token alone is no longer enough. An attacker needs the token and the key, and the key never travels.
Two standardised approaches implement this, and they suit different deployments.
Certificate-bound tokens (RFC 8705)
The client authenticates to the authorization server with a TLS client certificate. The authorization server records a hash of that certificate inside the issued token, in a confirmation claim. When the client later calls a resource server, it establishes a mutual TLS connection using the same certificate, and the resource server compares the hash in the token against the certificate on the live connection. A mismatch means rejection.
This is the stronger of the two mechanisms, because the binding is enforced by the TLS stack itself and the private key typically lives in a keystore or hardware module. It is also the harder to deploy, since it requires client certificate management and a TLS termination layer that preserves the certificate details for the application. Our write-up on mutual TLS covers that plumbing in more depth.
DPoP (RFC 9449)
Demonstrating Proof of Possession, published in 2023, achieves a similar binding at the application layer, with no changes to the TLS setup. The client generates a key pair, usually per session and often per browser tab.
On every request, the client sends an extra header containing a short JWT it signs with the private key. That proof carries the public key in its header, and its payload states the HTTP method, the target URI, a timestamp, a unique identifier, and, when an access token is being presented, a hash of that token. The authorization server records the public key's thumbprint in the access token. The resource server verifies the proof's signature, checks that the method and URI match the request it is actually serving, checks the timestamp is recent, checks it has not seen that identifier before, and confirms the proof's key thumbprint matches the one bound into the token.
Each of those checks closes a specific gap. Binding the method and URI stops a captured proof being replayed against a different endpoint. The timestamp and identifier limit replay against the same endpoint. The token hash stops a proof being paired with a different token.
A bearer token answers "is this token valid?" A sender-constrained token answers "is this token valid, and is the party presenting it the party it was issued to?" The second question is the one that matters after a token has leaked, which is the scenario every short-lifetime policy is implicitly planning for.
The mechanism that came first and died
Token Binding, standardised in 2018 across three RFCs, tried to solve this at the TLS layer for browsers, binding cookies and tokens to a key negotiated during the handshake. It was elegant and it did not survive contact with deployment. Chrome removed support in 2018, citing low adoption and complexity in the middlebox-heavy reality of the public internet. The specifications remain published and effectively unimplemented.
DPoP's design reads as a direct response to that failure. It operates entirely in HTTP headers, requires no TLS stack changes, survives termination at a load balancer or CDN, and can be adopted by one API at a time. Deployability was the design constraint, and the resulting mechanism is weaker than a TLS-layer binding in exchange for actually shipping.
| Property | Bearer | DPoP | mTLS-bound |
|---|---|---|---|
| Stolen token alone is usable | Yes | No | No |
| Requires TLS stack changes | No | No | Yes |
| Survives CDN or proxy termination | Yes | Yes | Needs header passthrough |
| Key can be non-extractable in a browser | Not applicable | Yes, via WebCrypto | Limited browser support |
| Per-request server state | None | Replay identifier cache | None |
Where it still falls short
This is the part that gets skipped in most introductions, and it determines whether the mechanism helps in your threat model or just adds moving parts.
The key is only as protected as its storage. DPoP in a browser is meaningful when the private key is generated as a non-extractable WebCrypto key. The page can sign with it and cannot read it, so a cross-site scripting flaw that would previously have exfiltrated the token now yields a key handle that stops working the moment the tab closes. Generate that same key as an extractable JWK in local storage and you have added a header without adding a security property, because the attacker takes the key along with the token.
An attacker with live code execution in the page can still sign. Non-extractable keys prevent theft, not use. Injected script can mint valid proofs for as long as it runs. The attack changes from "steal the token and use it from anywhere, for as long as it lives" to "operate from inside the victim's session while the session is open," which is a real downgrade in attacker capability and not a fix.
It does not stop the phishing that starts the problem. A relay proxy that walks a victim through a genuine login, described in our piece on adversary-in-the-middle phishing, can capture the resulting session. Sender-constraining limits what the captured artefact is worth afterwards. Origin-bound credentials at the authentication step, meaning hardware security keys, are what prevent the capture itself, and the two defences are complementary rather than alternatives.
Clock skew and replay caches are real operational costs. Proofs carry timestamps, so servers must accept a window, and that window is exactly how long a captured proof stays replayable against the same endpoint. Tightening it produces false rejections on clients with drifting clocks.
Where this is heading
Financial-grade API profiles have already made sender-constraining mandatory, requiring either mTLS or DPoP rather than treating it as an optional hardening step. That is the clearest signal of where the standards work considers the baseline to be.
The browser vendors are working the cookie half of the same problem. Device Bound Session Credentials is a proposal to bind session cookies to a key held in device hardware, so that a cookie copied to another machine stops authenticating. It targets the same attacker as DPoP, the one described in our piece on infostealer session hijacking, from the other direction: the API side gets proof-of-possession tokens, the browser side gets hardware-bound cookies.
The common thread is a retreat from credentials that work anywhere. Passwords, then bearer tokens, then session cookies: each generation of secret has proven copyable, and each replacement binds the secret to something that is harder to copy than a string.
If you are implementing it
- Generate the key non-extractably. In browsers this means WebCrypto with the extractable flag false, stored as a key handle. On mobile it means the platform keystore or secure enclave. This is the decision the security property depends on.
- Verify the full proof, not the signature alone. Method, URI, timestamp window, replay identifier, token hash, and thumbprint match. A verifier that only checks the signature has implemented a header, not a binding.
- Bind refresh tokens too. A sender-constrained access token beside a bearer refresh token leaves the more valuable credential unprotected.
- Keep the short lifetimes. Sender-constraining is a second layer, and it does not license longer-lived tokens.
- Watch the rejection rate after rollout. Clock skew and proxy header stripping both show up as authentication failures that look like client bugs, and both are worth catching before they reach users.
For the surrounding token questions, our write-ups on JWT security pitfalls and PASETO compared to JWT cover the format-level decisions that sit underneath any of this.