Skip to Content
Architecture BlueprintDPoP Session Binding

DPoP Session Binding & Security

Standard Bearer tokens (JWTs) are vulnerable to intercept-and-replay attacks: if a token is stolen from client storage or intercepted in transit, an attacker can reuse it from any machine without restriction.

NexID Auth-DPoP mitigates this vulnerability by implementing DPoP (Demonstrating Proof-of-Possession) as defined in RFC 9449. DPoP cryptographically binds the access token to an ephemeral client key pair.


1. DPoP Handshake & Validation Lifecycle

The sequence below illustrates how a client registers its key during token acquisition and subsequently proves possession of that key when accessing protected application resources.


2. Ephemeral Client Keypair Generation

When a client session is initialized:

  1. The client browser generates an asymmetric cryptographic key pair using the Web Crypto API.
  2. The key pair uses a secure curve like P-256 or Ed25519.
  3. The private key is flagged as non-extractable (extractable: false) so it cannot be read by malicious browser scripts or extension exploits.

3. DPoP Proof Generation

For every request sent to either the Central Auth Edge or a Downstream Application Server, the client must construct a short-lived DPoP Proof (a separate JWT).

DPoP Proof Headers

The proof includes the client’s public key as a JSON Web Key (jwk) in its header:

{ "typ": "dpop+jwt", "alg": "ES256", "jwk": { "kty": "EC", "x": "f83OJ3D2...", "y": "x_da65ld...", "crv": "P-256" } }

DPoP Proof Payload

The payload includes claims that bind the proof to a specific HTTP request and timestamp, limiting reuse:

{ "jti": "80231a4c-4eb2-411a-8c54-4a4b868e4a90", "htm": "POST", "htu": "https://auth.domain.com/auth/token", "iat": 1780339200 }
  • jti: A unique random identifier for this specific request.
  • htm: The HTTP method (e.g., POST, GET).
  • htu: The target HTTP URI (without query parameters).
  • iat: The issue time (typically valid for no more than 60–120 seconds).

4. Token Cryptographic Binding (cnf claim)

When the Central Auth Edge generates an access token, it computes the SHA-256 thumbprint of the client’s public key (the jkt value). This thumbprint is baked into the final Access Token’s payload under the confirmation (cnf) claim:

{ "sub": "user_12345", "aud": "app_1", "permissions": 43, "exp": 1782345600, "cnf": { "jkt": "0Z_A4_v81vB7L...T8pQ" } }

5. Server-Side Verification

When a downstream application server or edge worker receives a request containing a DPoP-bound token, it verifies:

  1. Access Token Validity: Verifies the signature of the main access token using the auth server’s public key (fetched from JWKS).
  2. DPoP Proof Signature: Extracts the public jwk from the DPoP proof header and uses it to verify the proof’s signature.
  3. Key Matching: Computes the SHA-256 thumbprint (jkt) of the public key inside the DPoP proof header and ensures it exactly matches the cnf.jkt claim inside the access token.
  4. Context Constraints: Matches the htm and htu claims in the proof payload with the actual incoming request’s method and URL.
  5. Replay Check: Queries Upstash Redis to ensure the jti has not been used before.
⚠️

JTI Replay Window To prevent replay attacks, the jti is cached in Redis with a time-to-live (TTL) of 2 minutes. Any request attempting to reuse a jti within this window is immediately rejected with a 401 Unauthorized status.