← Recipes

阅读中文版本

Bind Tokens to the Client with DPoP

Use a proof-of-possession key so a stolen bearer token cannot be replayed from another machine, and validate the proof and its replay id at the resource server.

~50 minauthenticationoauthsecurity

Task

Add DPoP sender-constrained tokens to make access tokens useless to a thief without the client key.

When to use this

A bearer token is copied from a device or log and replayed by an attacker, because possession of the token alone is enough to call the API.

Prerequisites

  • An authorization server that supports DPoP
  • A client able to generate and retain an asymmetric key

Steps

1. Generate a client key pair and send the public key in a DPoP proof with the token request.

Expected output: The token is bound to the client key.

2. Attach a fresh DPoP proof to each API request.

Expected output: Every request proves possession of the private key.

3. Validate the proof signature, the token thumbprint, and the jti replay id at the resource server.

Expected output: Replayed proofs are rejected.

4. Reject requests whose proof does not match the token binding.

curl -H "Authorization: DPoP $TOKEN" -H "DPoP: $PROOF" https://api.example.com/data

Expected output: A stolen token alone cannot be used.

Troubleshooting

Problem

Clock skew rejects valid proofs

Solution

Allow a small time window and validate the iat with a defined tolerance.

Problem

Nonce required by server

Solution

Implement the server-provided nonce challenge and retry with the nonce included.

Limitations

  • DPoP requires client-side key management and adds a proof per request, increasing complexity.
  • Not all clients support it, so a migration period with both bearer and DPoP tokens may be needed.

Context

Add DPoP sender-constrained tokens to make access tokens useless to a thief without the client key. A bearer token is copied from a device or log and replayed by an attacker, because possession of the token alone is enough to call the API.

Steps

  1. Generate a client key pair and send the public key in a DPoP proof with the token request. Expected: The token is bound to the client key.
  2. Attach a fresh DPoP proof to each API request. Expected: Every request proves possession of the private key.
  3. Validate the proof signature, the token thumbprint, and the jti replay id at the resource server. Expected: Replayed proofs are rejected.
  4. Reject requests whose proof does not match the token binding. Expected: A stolen token alone cannot be used.
const proof = await makeDpopProof({ htm: 'GET', htu: url, key: privateKey });
fetch(url, {
  headers: { Authorization: `DPoP ${token}`, DPoP: proof },
});

Verify

  • The token is bound to the client key.
  • Every request proves possession of the private key.
  • Replayed proofs are rejected.
  • A stolen token alone cannot be used.

Limits

  • DPoP requires client-side key management and adds a proof per request, increasing complexity.
  • Not all clients support it, so a migration period with both bearer and DPoP tokens may be needed.