{"slug": "what-is-dpop-rfc-9449-sender-constrained-tokens-for-ai-agents-explained", "title": "What Is DPoP (RFC 9449)? Sender-Constrained Tokens for AI Agents Explained", "summary": "DPoP, the sender-constraining mechanism defined in RFC 9449, binds OAuth 2.0 access tokens to a client-held private key so that a leaked token cannot be used without a matching proof JWT, addressing a risk the RFC's explainer says is acute for AI agents whose tokens pass through prompts, tool outputs, transcripts and telemetry. Each request carries a proof in a DPoP header containing a jti with at least 96 bits of randomness, the htm method, the htu target URL, an iat timestamp and, for API calls, an ath claim holding the base64url SHA-256 hash of the access token; the authorization server ties the token to the key via the cnf.jkt confirmation claim and returns token_type \"DPoP\". Servers can demand a nonce, replying with HTTP 400 and the use_dpop_nonce error plus a DPoP-Nonce header (401 from a resource server), and Personal Agent Protocol Draft 0.1 binds its short-lived session tokens to a DPoP key while allowing bearer tokens only for MCP servers, which it flags as less secure.", "body_md": "# What Is DPoP (RFC 9449)? Sender-Constrained Tokens for AI Agents Explained\n\nDPoP (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.\n\nDPoP, 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.\n\n## The problem: bearer tokens\n\nA 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.\n\n## How it works, step by step\n\n1. **The client makes a key pair** and keeps the private key.\n2. **For each request, it signs a short proof** (a JWT) and sends it in a`DPoP` header.\n3. **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).\n4. **The client calls the API** with`Authorization: DPoP <token>` plus a fresh proof.\n5. **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.\n\nA thief with only the token cannot create a valid proof, so the token fails.\n\n## What is inside a proof\n\nThe proof is a JWT with a header and claims (RFC 9449 section 4.2):\n\n| Part | Value | Why | \n|---|---|---|\n| Header `typ` | `dpop+jwt` | Marks it as a DPoP proof | \n| Header `alg` | An asymmetric algorithm, never `none` | The signature type | \n| Header `jwk` | The client's public key | Lets the server verify the signature and compute the thumbprint | \n| `jti` | A unique ID with at least 96 bits of randomness | Lets servers reject a replayed proof | \n| `htm` | The HTTP method | Ties the proof to this request type | \n| `htu` | The target URL, without query or fragment | Ties the proof to this endpoint, so it cannot be reused elsewhere | \n| `iat` | Creation time | Keeps proofs fresh | \n| `ath` | Base64url SHA-256 hash of the access token | Present when calling an API; ties the proof to one token | \n| `nonce` | A server-provided value | Present when the server asks for one | \n\nAn illustrative decoded proof for an API call (all values fictional):\n\n```\n{\n  \"header\": { \"typ\": \"dpop+jwt\", \"alg\": \"ES256\", \"jwk\": { \"kty\": \"EC\", \"crv\": \"P-256\", \"x\": \"…\", \"y\": \"…\" } },\n  \"claims\": {\n    \"jti\": \"f3a9c1d2-7b64-4e0a-9d55-2c8b1e7a0001\",\n    \"htm\": \"GET\",\n    \"htu\": \"https://api.example.test/orders/1042\",\n    \"iat\": 1791555000,\n    \"ath\": \"fUHyO2r2Z3DZ53EsNrWBb0xWXoaNy59IiKCAqksmQEo\"\n  }\n}\n```\n\n## Server nonces\n\nA 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`.\n\n## Where DPoP appears in the agent world\n\n- **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) .\n- **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) .\n- **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.\n\n## DPoP vs plain bearer vs mutual TLS\n\n|  | Bearer token | DPoP | Mutual-TLS-bound token | \n|---|---|---|---|\n| Stolen token alone is usable | Yes | No | No | \n| Binding is to | Nothing | A key held by the client | A client certificate | \n| Works at application level | Yes | Yes | Needs TLS-level client certificates | \n| Server field to look for | none | `dpop_signing_alg_values_supported` | `tls_client_certificate_bound_access_tokens` | \n\n## What DPoP does not do\n\nThe RFC is direct about limits:\n\n- It is \"not a substitute for a secure transport\" and must always be used with HTTPS.\n- 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.\n- It does not protect request integrity. A proof covers the method and URL, not the body or other headers (section 11.7).\n\n## How to check a server\n\nLook 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.\n\n## Sources\n\nFollow Trango Compute on LinkedIn\n\nWe post updates on new tools, context engineering patterns, and LLM cost research.\n\n[Follow on LinkedIn](https://www.linkedin.com/company/trango-compute)", "url": "https://wpnews.pro/news/what-is-dpop-rfc-9449-sender-constrained-tokens-for-ai-agents-explained", "canonical_source": "https://contextiq.trango-compute.com/blog/what-is-dpop-rfc-9449-sender-constrained-tokens-ai-agents", "published_at": "2026-10-09 00:00:00+00:00", "updated_at": "2026-10-28 15:11:58.912730+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-safety"], "entities": ["DPoP", "RFC 9449", "OAuth 2.0", "Personal Agent Protocol", "MCP", "FAPI 2.0"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/what-is-dpop-rfc-9449-sender-constrained-tokens-for-ai-agents-explained", "markdown": "https://wpnews.pro/news/what-is-dpop-rfc-9449-sender-constrained-tokens-for-ai-agents-explained.md", "text": "https://wpnews.pro/news/what-is-dpop-rfc-9449-sender-constrained-tokens-for-ai-agents-explained.txt", "jsonld": "https://wpnews.pro/news/what-is-dpop-rfc-9449-sender-constrained-tokens-for-ai-agents-explained.jsonld"}}