Web Security

Sender-Constrained Tokens: Making a Stolen Access Token Useless

August 13, 2026 9 min read Haven Team

Most of the web runs on bearer tokens. The name is precise and the implication is uncomfortable: whoever bears the token gets the access. An attacker who lifts one from a log file, a browser's storage, or an intercepted request does not need your password, your second factor, or your device. They need a copy of a string. Two IETF specifications exist to break that equivalence, and one of them is now common enough to be worth understanding in detail.


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.

The shift in what the server verifies

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

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.

Try Haven free for 15 days

Encrypted email and chat in one app. No credit card required.

Get Started →