# What Is DPoP (RFC 9449)? Sender-Constrained Tokens for AI Agents Explained

> Source: <https://contextiq.trango-compute.com/blog/what-is-dpop-rfc-9449-sender-constrained-tokens-ai-agents>
> Published: 2026-10-09 00:00:00+00:00

# What Is DPoP (RFC 9449)? Sender-Constrained Tokens for AI Agents Explained

DPoP (RFC 9449) ties an OAuth access token to a key so a stolen token fails: proof JWTs, cnf jkt, server nonces, and how PAP, FAPI 2.0 and MCP servers use it.

DPoP, short for Demonstrating Proof of Possession, is defined in [RFC 9449](https://www.rfc-editor.org/rfc/rfc9449.html). The RFC's abstract calls it "a mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level." In plain terms: an access token stops being a password-like string that works for whoever holds it, and becomes a token that only works together with a private key the legitimate client keeps.

## The problem: bearer tokens

A normal OAuth access token is a bearer token. Anyone who gets a copy can use it until it expires. Tokens leak through logs, error reports, URLs, browser extensions and compromised servers. This is a particular concern for AI agents, whose tokens can pass through prompts, tool outputs, transcripts and telemetry. DPoP does not stop a leak. It makes a leaked token far less useful.

## How it works, step by step

1. **The client makes a key pair** and keeps the private key.
2. **For each request, it signs a short proof** (a JWT) and sends it in a`DPoP` header.
3. **The authorization server binds the token to the key.** The token response has`token_type: "DPoP"` , and the access token is tied to the public key through a confirmation claim,`cnf.jkt` , which is a thumbprint of that key (RFC 9449 section 6.1).
4. **The client calls the API** with`Authorization: DPoP <token>` plus a fresh proof.
5. **The resource server checks** that the proof is valid, that its key matches the one the token is bound to, and that the proof matches this request.

A thief with only the token cannot create a valid proof, so the token fails.

## What is inside a proof

The proof is a JWT with a header and claims (RFC 9449 section 4.2):

| Part | Value | Why | 
|---|---|---|
| Header `typ` | `dpop+jwt` | Marks it as a DPoP proof | 
| Header `alg` | An asymmetric algorithm, never `none` | The signature type | 
| Header `jwk` | The client's public key | Lets the server verify the signature and compute the thumbprint | 
| `jti` | A unique ID with at least 96 bits of randomness | Lets servers reject a replayed proof | 
| `htm` | The HTTP method | Ties the proof to this request type | 
| `htu` | The target URL, without query or fragment | Ties the proof to this endpoint, so it cannot be reused elsewhere | 
| `iat` | Creation time | Keeps proofs fresh | 
| `ath` | Base64url SHA-256 hash of the access token | Present when calling an API; ties the proof to one token | 
| `nonce` | A server-provided value | Present when the server asks for one | 

An illustrative decoded proof for an API call (all values fictional):

```
{
  "header": { "typ": "dpop+jwt", "alg": "ES256", "jwk": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" } },
  "claims": {
    "jti": "f3a9c1d2-7b64-4e0a-9d55-2c8b1e7a0001",
    "htm": "GET",
    "htu": "https://api.example.test/orders/1042",
    "iat": 1791555000,
    "ath": "fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo"
  }
}
```

## Server nonces

A client can sign proofs ahead of time, or an attacker who controls the client can generate them for the future. To prevent that, a server may require a nonce it chooses. The RFC describes the flow: an authorization server replies with HTTP 400, the error `use_dpop_nonce` and a `DPoP-Nonce` header (a resource server uses a 401 challenge instead); the client retries with that value in the proof's `nonce` claim (section 8). Invalid proofs get `invalid_dpop_proof`.

## Where DPoP appears in the agent world

- **Personal Agent Protocol.** The PAP Draft 0.1[specification](https://personalagentprotocol.org/docs/spec) makes its short-lived session tokens bound to a DPoP key. Its one exception is MCP servers, where it allows bearer tokens and flags them as less secure. See[what PAP is](https://contextiq.trango-compute.com/blog/what-is-personal-agent-protocol-pap-sierra-meta) .
- **FAPI 2.0.** The OpenID Foundation's[FAPI 2.0 Security Profile](https://openid.net/specs/fapi-security-profile-2_0.html) requires sender-constrained access tokens, using either mutual TLS (RFC 8705) or DPoP. See[FAPI and agent checkout](https://contextiq.trango-compute.com/blog/openid-fapi-openai-acp-api-security-agent-checkout) .
- **OAuth metadata.** A server advertises support with`dpop_signing_alg_values_supported` in its authorization server metadata (RFC 9449 section 5.1). A protected resource can publish`dpop_bound_access_tokens_required` in its RFC 9728 metadata to say it always requires DPoP-bound tokens.

## DPoP vs plain bearer vs mutual TLS

|  | Bearer token | DPoP | Mutual-TLS-bound token | 
|---|---|---|---|
| Stolen token alone is usable | Yes | No | No | 
| Binding is to | Nothing | A key held by the client | A client certificate | 
| Works at application level | Yes | Yes | Needs TLS-level client certificates | 
| Server field to look for | none | `dpop_signing_alg_values_supported` | `tls_client_certificate_bound_access_tokens` | 

## What DPoP does not do

The RFC is direct about limits:

- It is "not a substitute for a secure transport" and must always be used with HTTPS.
- It does not protect a client whose private key is stolen, or malicious code running inside the client. Code in the client's context can create proofs, including future-dated ones, and pre-generated proofs are possible. Server-chosen nonces help.
- It does not protect request integrity. A proof covers the method and URL, not the body or other headers (section 11.7).

## How to check a server

Look at what an authorization server publishes. The [OIDC Inspector](https://contextiq.trango-compute.com/oidc-inspector) has a "FAPI 2.0 Metadata Signals" card that reports whether `dpop_signing_alg_values_supported` and `tls_client_certificate_bound_access_tokens` are published. For an OAuth-protected MCP server, the [Agent Protocol Inspector](https://contextiq.trango-compute.com/agent-readiness-detector) shows the same signals for its authorization server. Two cautions: a field that is not published does not prove the capability is missing, and a published field is a claim, not proof the server enforces it. Neither tool tests DPoP behaviour or performs a conformance check.

## Sources

Follow Trango Compute on LinkedIn

We post updates on new tools, context engineering patterns, and LLM cost research.

[Follow on LinkedIn](https://www.linkedin.com/company/trango-compute)
