Back to HTTP Headers

DPoP auth request

Proof-of-possession header that cryptographically binds a request to a client-held key, preventing stolen Bearer tokens from being replayed by an attacker.

What it does

DPoP (Demonstrating Proof-of-Possession) is a request header that cryptographically proves the client making the request actually holds the private key associated with its access token — addressing a real, well-known weakness of plain OAuth Bearer tokens. A Bearer token, as the name implies, grants access to whoever "bears" it: if a Bearer token is stolen (leaked in logs, intercepted, exfiltrated via XSS), an attacker can simply replay it and be treated as the legitimate client, no further proof required.

DPoP fixes this by requiring every request to include a fresh, signed JWT proving possession of a private key — an attacker who steals just the access token (without also stealing the private key) can't successfully replay it, since they can't produce a valid DPoP proof.

Syntax

DPoP: <signed-jwt>

The value is a JWT with a specific structure, signed by the client's private key:

// Header
{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
}

// Payload
{
  "jti": "unique-request-id",
  "htm": "POST",
  "htu": "https://api.example.com/resource",
  "iat": 1723999999
}
  • jwk — the client's public key (embedded so the server can verify the signature)
  • jti — a unique identifier for this specific proof, preventing replay of the exact same DPoP proof
  • htm/htu — the HTTP method and URL this proof is bound to, preventing a proof generated for one endpoint from being reused on another
  • iat — issued-at timestamp, letting servers reject stale proofs

How it works with Authorization

DPoP-bound tokens are used alongside Authorization, not instead of it:

POST /api/transfer HTTP/1.1
Authorization: DPoP eyJhbGc...access-token...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2In0...proof-jwt...

Note the Authorization scheme changes from Bearer to DPoP when the token is DPoP-bound — this signals to the server that it should also verify the accompanying DPoP proof header, not just validate the token in isolation.

The server checks that the public key embedded in the DPoP JWT matches the key the access token was originally issued to (typically via a thumbprint claim baked into the token at issuance time), confirming the token and the proof genuinely belong to the same client.

Common mistakes and gotchas

Reusing the same DPoP proof across multiple requests. Each proof is meant to be single-use, tied to a unique jti and a specific method/URL — reusing one is both a spec violation and defeats much of the replay protection DPoP is meant to provide. A new signed proof must be generated for every request.

Not implementing nonce support when required. Some authorization servers require a server-provided nonce (delivered via the separate DPoP-Nonce header) to be included in subsequent proofs, adding another layer of freshness guarantee — clients that don't handle this nonce challenge-response correctly will have their DPoP-bound requests rejected.

Confusing DPoP with mTLS-based token binding. Both solve a similar underlying problem (preventing Bearer token replay), but through very different mechanisms — mTLS binds tokens to a TLS client certificate at the connection layer, while DPoP binds them to an application-layer JWT proof per request. They're not interchangeable, and choosing between them depends on your infrastructure's existing capabilities (mTLS support vs. simpler public-key JWT signing).

Underestimating the client-side key management complexity. DPoP requires the client to securely generate, store, and use a private key for signing — this is genuinely more implementation complexity than a plain Bearer token flow, and getting key storage wrong (storing the private key somewhere accessible to XSS, for instance) undermines the entire security benefit DPoP is meant to provide.

Real-world examples

A DPoP-bound API request:

GET /api/account/balance HTTP/1.1
Authorization: DPoP eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIn0.signature
DPoP: eyJ0eXAiOiJkcG9wK2p3dCJ9.eyJqdGkiOiJhYmMxMjMiLCJodG0iOiJHRVQiLCJodHUiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbS9hcGkvYWNjb3VudC9iYWxhbmNlIiwiaWF0IjoxNzIzOTk5OTk5fQ.proof-signature

Server rejecting a stolen token used without a valid matching proof:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: DPoP error="invalid_dpop_proof"

FAQ

Does DPoP replace OAuth Bearer tokens entirely?

No — it's a binding mechanism layered on top of the existing OAuth token flow. The access token still exists and is still sent (via the Authorization header, using the DPoP scheme instead of Bearer), just accompanied by cryptographic proof that the client presenting it actually holds the associated private key.

What happens if an attacker steals a DPoP-bound access token?

Without also stealing the client's private key, the attacker can't generate valid DPoP proofs, so a replayed token alone is rejected — this is precisely the security improvement over plain Bearer tokens, where possessing the token string is sufficient for a successful replay.

Do I need a new DPoP proof for every single request?

Yes — proofs are meant to be single-use and freshly signed for each request, bound to that specific request's method and URL via the jti, htm, and htu claims.

Is DPoP widely adopted yet?

Adoption is growing, particularly among OAuth providers prioritizing stronger token security, but it's not yet as universal as plain Bearer tokens — check whether your specific authorization server and client libraries support it before building around the assumption.

Fun fact

DPoP's design deliberately avoids requiring mutual TLS (mTLS) infrastructure, which is the other major standardized approach to token binding (RFC 8705) — mTLS binding is powerful but requires client certificate infrastructure that's often impractical for browser-based or mobile applications. DPoP was specifically designed to achieve similar proof-of-possession security using ordinary application-layer JavaScript/mobile cryptography APIs, making it accessible to a much broader range of client environments than mTLS-based binding ever could be.