Flows / Cross-cutting

Sender-constrained tokens (DPoP)

Bind a token to a key so that stealing the token is not enough to use it.

Based onRFC 9449published September 2023Guide reviewedNovember 2026

Sources and review details
This page
Implementation guideAn explanation, not a specification
Primary specification
DPoPRFC · September 2023
All 4 specifications
4 published
Flow maturity
Stable standardsWhat the rating means
Human involvement
With or without a human
Review
Checked against sources in November 2026How it was checked

A bearer token works like cash: whoever holds it can spend it. That was an acceptable trade when tokens lived in a server’s memory. Agents put tokens in riskier places. They pass through tool calls, logs, traces, proxies and sometimes the model’s own context, and an agent that reads untrusted content can be talked into revealing what it holds.

Demonstrating Proof of Possession fixes the cash problem at the application layer. The agent generates a key pair, and the authorization server binds each token to the public key. Every request then carries a small signed proof made with the private key. A stolen token without the key is rejected.

Mutual TLS does the same job at the transport layer and is a good fit where a service mesh or workload identity system already manages certificates. DPoP works where the agent cannot control TLS: public clients, desktop apps, anything behind a proxy that terminates connections.

Step by step

7 steps between 3 parties. Each step shows the message and who holds which credential afterward. Use ←→ or select any arrow.

Sequence diagram3 participants, 7 steps
  • Direct call
  • Response
  • Local action

Drag to see every party and step. Tap a step to jump to its explanation.

1Generate a key pair2Token request + DPoP proof3Token bound tokey (cnf.jkt)4Sign a fresh proof5Request + token + proof6Match proof key to cnf.jkt7200 OK

  1. 1AgentLocal action

    The agent creates a key

    One key pair per agent instance, kept for the life of the session or longer. The private key should never be readable by the model or written to a log.

    Who holds what after this step

    Agent
    newprivate key

    Parties not listed hold none of this flow’s credentials at this point.

  2. 2Agent to Authorization serverDirect call

    The token request carries a proof

    Any grant type works. The extra piece is the DPoP header: a JWT that includes the public key and is signed with the private key. It names the HTTP method and URL it is for, and has a unique jti so it cannot be replayed.

    POST /token HTTP/1.1
    Host: auth.example
    Content-Type: application/x-www-form-urlencoded
    DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7…
    
    grant_type=authorization_code&code=…&code_verifier=…

    Who holds what after this step

    Agent
    private key

    Parties not listed hold none of this flow’s credentials at this point.

  3. 3Authorization server to AgentResponse

    The server binds the token to the key

    The access token contains a confirmation claim with the thumbprint of the agent’s public key. The response says token_type: DPoP instead of Bearer.

    {
      "sub": "user:alice",
      "aud": "https://calendar.example/api",
      "exp": 1794269400,
      "cnf": {
        "jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
      }
    }

    Who holds what after this step

    Agent
    private keynewaccess token (bound to key)

    Parties not listed hold none of this flow’s credentials at this point.

  4. 4AgentLocal action

    A new proof for every request

    Before each API call the agent signs a new proof. It covers the method (htm), the URL (htu), the time, a fresh jti, and a hash of the access token (ath) so the proof cannot be paired with a different token.

    {
      "typ": "dpop+jwt", "alg": "ES256",
      "jwk": { "kty": "EC", "crv": "P-256", "x": "l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs", "y": "9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA" }
    }
    .
    {
      "jti": "e1j3V_bKic8-LAEB",
      "htm": "GET",
      "htu": "https://calendar.example/api/events",
      "iat": 1794268830,
      "ath": "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
    }

    Who holds what after this step

    Agent
    private keyaccess token (bound to key)

    Parties not listed hold none of this flow’s credentials at this point.

  5. 5Agent to APIDirect call

    The agent sends token and proof together

    Note the DPoP authorization scheme. A resource server that sees a DPoP-bound token presented as Bearer must reject it.

    GET /api/events HTTP/1.1
    Host: calendar.example
    Authorization: DPoP eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9…
    DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7…

    Who holds what after this step

    Agent
    private keyaccess token (bound to key)

    Parties not listed hold none of this flow’s credentials at this point.

  6. 6APILocal action

    The API checks that the caller holds the key

    It verifies the proof’s signature with the key inside it, checks that the key’s thumbprint equals cnf.jkt in the token, that method and URL match this request, that the proof is recent, and that jti has not been seen before.

    Who holds what after this step

    Agent
    private keyaccess token (bound to key)

    Parties not listed hold none of this flow’s credentials at this point.

  7. 7API to AgentResponse

    The request succeeds

    An attacker who copied the access token from a log gets nothing: they cannot produce a proof without the private key, and a captured proof is useless for any other method, URL or moment.

    Who holds what after this step

    Agent
    private keyaccess token (bound to key)

    Parties not listed hold none of this flow’s credentials at this point.

Watch out for

  • DPoP protects the token, not the key. If the private key sits in the same file as the token, the thief takes both. Use a platform keystore or non-exportable key where one exists.
  • It does not stop a compromised agent from using its own key. It stops a token from being useful somewhere else.
  • Both the authorization server and the resource server must support it. A resource server that ignores the binding and accepts the token as a plain bearer token has gained nothing.
  • Proofs are tied to method and URL, and servers may require a nonce. Clock skew and proxies that rewrite URLs are the usual sources of trouble.

DPoP, mutual TLS, or message signatures

All three prove that the caller holds a key. Mutual TLS (RFC 8705) binds the token to a client certificate; it is strong and invisible to application code, but breaks when something in the path terminates TLS. DPoP signs a small proof per request and survives proxies. HTTP Message Signatures (RFC 9421) sign chosen parts of the request itself, including the body, and are the basis for newer work such as Web Bot Auth and workload proof tokens.

Standards in play

See the full tracker for status and links.

RFCRFC 9449

DPoP

Makes a token copied from a log, a trace or a model’s context useless without the key.

RFCRFC 7638

JWK Thumbprint

How DPoP and related schemes refer to the key a token is bound to.