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.
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/dataExpected 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
- 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.
- Attach a fresh DPoP proof to each API request. Expected: Every request proves possession of the private key.
- Validate the proof signature, the token thumbprint, and the jti replay id at the resource server. Expected: Replayed proofs are rejected.
- 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.