DPoP
Overview
Section titled “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 only when the configured CryptoProvider implements the optional DPoP extension.
Initialization
Section titled “Initialization”Initialize DPoP before making authentication requests:
const dpop = client.dpop;if (!dpop) { throw new Error('DPoP is not supported by this CryptoProvider');}
await dpop.initialize();
if (dpop.isInitialized()) { console.log('DPoP ready'); console.log('Thumbprint:', dpop.getThumbprint());}The SDK generates an asymmetric key pair and stores it for subsequent proof generation.
Generating DPoP Proofs
Section titled “Generating DPoP Proofs”Generate a proof JWT for each HTTP request:
const proof = await 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 dpop.calculateAccessTokenHash(accessToken);
const proof = await dpop.generateProof('GET', 'https://api.example.com/data', { accessTokenHash: tokenHash,});Proof Options
Section titled “Proof Options”| Parameter | Type | Description |
|---|---|---|
accessTokenHash | string | Base64url-encoded SHA-256 hash of the access token |
nonce | string | Server-provided nonce value |
Nonce Handling
Section titled “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 headerdpop.handleNonceResponse(nonceFromServer);
// Subsequent proofs will include the nonceconst proof = await dpop.generateProof('POST', 'https://auth.example.com/token');Checking Server Support
Section titled “Checking Server Support”Verify that the authorization server supports DPoP:
const supported = await dpop.isServerSupported();
if (supported) { await dpop.initialize();}This checks the dpop_signing_alg_values_supported field in the OIDC Discovery document.
DPoP Proof JWT Structure
Section titled “DPoP Proof JWT Structure”Each proof is a signed JWT with the following structure:
Header
Section titled “Header”{ "typ": "dpop+jwt", "alg": "ES256", "jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }}Claims
Section titled “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
Section titled “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, dpop: { tokenRequests: true, algorithm: 'ES256', },});
const dpop = client.dpop;if (!dpop) { throw new Error('DPoP is not supported by this CryptoProvider');}
// Initialize DPoP when the server advertises support.if (await dpop.isServerSupported()) { await 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);
// With dpop.tokenRequests enabled, token endpoint requests include DPoP proofs.// If the server issues a DPoP-bound token, token_type is 'DPoP'.const accessToken = await client.token.getAccessToken();Clearing DPoP State
Section titled “Clearing DPoP State”To reset the DPoP key pair (e.g., on logout):
await dpop.clear();Public Key Access
Section titled “Public Key Access”Retrieve the current DPoP public key:
const jwk = dpop.getPublicKeyJwk();console.log(jwk); // { kty: 'EC', crv: 'P-256', x: '...', y: '...' }
const thumbprint = dpop.getThumbprint();console.log(thumbprint); // JWK thumbprint stringError Handling
Section titled “Error 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
Section titled “References”- RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- DPoP feature overview — Server-side DPoP documentation
Next Steps
Section titled “Next Steps”- PAR — Pushed Authorization Requests
- JAR & JARM — Signed authorization requests and responses
- Token Management — Token lifecycle management