DPoP binds access tokens to your client so stolen tokens are useless to attackers.
Normal OAuth tokens are "bearer" tokens, whoever has the token can use it. If your token leaks through logs, network interception, or a compromised server, an attacker can use it until it expires.
DPoP ties each token to a cryptographic key pair. Your app proves it holds the private key on every request. Steal the token? Doesn't matter - you can't use it without the key.
Think of it like chip + PIN vs. a credit card number. Anyone can use a stolen card number. With chip + PIN, you need the physical card.
Use DPoP for financial transactions, sensitive data access, or any scenario where token theft would be a big problem.
- Generate a key pair for your client
- On token requests, include a signed DPoP proof JWT in the
DPoPheader - The server binds your token to your public key
- On API requests, include both the token and a fresh DPoP proof
- The resource server verifies the proof matches the token's bound key
The proof is a JWT with your public key in the header:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}The payload ties the proof to a specific request:
{
"jti": "unique-id",
"htm": "GET",
"htu": "https://api.business.jiko.io/api/v2/pockets/",
"iat": 1711910400
}jti- unique ID (prevents replay)htm- HTTP methodhtu- URL you're callingiat- timestamp (10 second leeway)
Include the DPoP proof when getting tokens:
POST /api/oauth2/token HTTP/1.1
Host: auth.jiko.io
Content-Type: application/x-www-form-urlencoded
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2In0...
grant_type=authorization_code&
client_id=your-client-id&
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&
client_assertion=eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9...Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
"token_type": "DPoP",
"expires_in": 900
}Note token_type is DPoP, not Bearer.
Every request needs the token and a fresh proof:
GET /api/v2/pockets/ HTTP/1.1
Host: api.business.jiko.io
Authorization: DPoP eyJhbGciOiJSUzI1NiIsInR5cCI...
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2In0...Generate a new proof for each request with a unique jti, correct htm/htu, and fresh iat.
| Threat | Bearer Token | DPoP Token |
|---|---|---|
| Token in logs | Attacker can use it | Useless without key |
| Token intercepted | Attacker can use it | Useless without key |
| Token leaked via XSS | Attacker can use it | Useless without key |
| Replay attacks | Works until expiry | Blocked by unique jti |
- RFC 9449 - DPoP specification
- OAuth 2.0 Security BCP - recommends sender-constrained tokens