Skip to content

DPoP (Demonstrating Proof of Possession)

DPoP binds access tokens to your client so stolen tokens are useless to attackers.


Why DPoP?

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.


How It Works

  1. Generate a key pair for your client
  2. On token requests, include a signed DPoP proof JWT in the DPoP header
  3. The server binds your token to your public key
  4. On API requests, include both the token and a fresh DPoP proof
  5. The resource server verifies the proof matches the token's bound key

DPoP Proof Structure

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 method
  • htu - URL you're calling
  • iat - timestamp (10 second leeway)

Token Request

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.


API Requests

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.


What DPoP Protects Against

ThreatBearer TokenDPoP Token
Token in logsAttacker can use itUseless without key
Token interceptedAttacker can use itUseless without key
Token leaked via XSSAttacker can use itUseless without key
Replay attacksWorks until expiryBlocked by unique jti

References