DPoP
Overview
DPoP (Demonstrating Proof of Possession, RFC 9449) binds access tokens to a client’s cryptographic key pair, preventing token theft and replay attacks. Even if an access token is intercepted, it cannot be used without the corresponding private key.
The DPoP API is available at client.dpop.
Initialization
Initialize DPoP before making authentication requests:
await client.dpop.initialize();
if (client.dpop.isInitialized()) { console.log('DPoP ready'); console.log('Thumbprint:', client.dpop.getThumbprint());}The SDK generates an asymmetric key pair and stores it for subsequent proof generation.
Generating DPoP Proofs
Generate a proof JWT for each HTTP request:
const proof = await client.dpop.generateProof('POST', 'https://auth.example.com/token');For resource requests that include an access token, pass the token to generate the ath (access token hash) claim:
const accessToken = await client.token.getAccessToken();const tokenHash = await client.dpop.calculateAccessTokenHash(accessToken);
const proof = await client.dpop.generateProof('GET', 'https://api.example.com/data', { accessTokenHash: tokenHash,});Proof Options
| Parameter | Type | Description |
|---|---|---|
accessTokenHash | string | Base64url-encoded SHA-256 hash of the access token |
nonce | string | Server-provided nonce value |
Nonce Handling
Authorization servers may require a nonce in DPoP proofs. When the server returns a DPoP-Nonce header, pass it to the SDK:
// Server responds with DPoP-Nonce headerclient.dpop.handleNonceResponse(nonceFromServer);
// Subsequent proofs will include the nonceconst proof = await client.dpop.generateProof('POST', 'https://auth.example.com/token');Checking Server Support
Verify that the authorization server supports DPoP:
const supported = await client.dpop.isServerSupported();
if (supported) { await client.dpop.initialize();}This checks the dpop_signing_alg_values_supported field in the OIDC Discovery document.
DPoP Proof JWT Structure
Each proof is a signed JWT with the following structure:
Header
{ "typ": "dpop+jwt", "alg": "ES256", "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }}Claims
{ "jti": "unique-id", "htm": "POST", "htu": "https://auth.example.com/token", "iat": 1699876543, "ath": "base64url-sha256-of-access-token", "nonce": "server-provided-nonce"}| Claim | Type | Description |
|---|---|---|
jti | string | Unique proof identifier (prevents replay) |
htm | string | HTTP method (uppercase) |
htu | string | HTTP URL (scheme + host + path, no query) |
iat | number | Issued at timestamp |
ath | string | Access token hash (when using access token) |
nonce | string | Server-provided nonce (when required) |
Complete Example: Token Request with DPoP
const client = await createAuthrimClient({ issuer: 'https://auth.example.com', clientId: 'my-app', crypto: cryptoProvider, // Must implement DPoPCryptoProvider storage: storageProvider, http: httpClient,});
// Initialize DPoPif (await client.dpop.isServerSupported()) { await client.dpop.initialize();}
// Build authorization URL (DPoP is transparent to the auth flow)const { url } = await client.buildAuthorizationUrl({ redirectUri: 'https://myapp.com/callback',});
// After callback, tokens are automatically DPoP-boundconst tokens = await client.handleCallback(callbackUrl);
// Access token is now DPoP-bound (token_type: 'DPoP')// The SDK automatically generates DPoP proofs when fetching tokensconst accessToken = await client.token.getAccessToken();Clearing DPoP State
To reset the DPoP key pair (e.g., on logout):
await client.dpop.clear();Public Key Access
Retrieve the current DPoP public key:
const jwk = client.dpop.getPublicKeyJwk();console.log(jwk); // { kty: 'EC', crv: 'P-256', x: '...', y: '...' }
const thumbprint = client.dpop.getThumbprint();console.log(thumbprint); // JWK thumbprint stringError Handling
| Error Code | Description | Recovery |
|---|---|---|
dpop_key_generation_error | Failed to generate the key pair | Check CryptoProvider implementation |
dpop_proof_generation_error | Failed to sign the proof JWT | Check key availability, reinitialize |
References
- RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- DPoP feature overview — Server-side DPoP documentation
Next Steps
- PAR — Pushed Authorization Requests
- JAR & JARM — Signed authorization requests and responses
- Token Management — Token lifecycle management