This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
securitycritical PriorityID: webauthn-conditional-mediation-dpop-binding

WebAuthn Conditional Mediation with RFC 9449 DPoP Cryptographic Token Binding

Enforces immutable cryptographic binding between WebAuthn conditional mediation (passkey autofill) assertions and RFC 9449 Demonstrating Proof-of-Possession (DPoP) key pairs. Eliminates bearer token replay, session fixation, and adversary-in-the-middle (AiTM) hijacking by anchoring issued session tokens to an ephemeral, client-held private key thumbprint (jkt) verified on every downstream request.

Prohibited Anti-Pattern: Exchanging WebAuthn passkey assertions for unbound bearer tokens or ambient cookies vulnerable to AiTM replay and exfiltration

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: WebAuthn Conditional Mediation with RFC 9449 DPoP Cryptographic Token Binding - Enforces immutable cryptographic binding between WebAuthn conditional mediation (passkey autofill) assertions and RFC 9449 Demonstrating Proof-of-Possession (DPoP) key pairs. Eliminates bearer token replay, session fixation, and adversary-in-the-middle (AiTM) hijacking by anchoring issued session tokens to an ephemeral, client-held private key thumbprint (jkt) verified on every downstream request.
globs: src/auth/**/*.{ts,tsx}, src/server/auth/**/*.{ts,tsx}, src/api/auth/**/*.{ts,tsx}, packages/auth/**/*.{ts,tsx}, apps/api/src/modules/auth/**/*.{ts,tsx}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/webauthn-conditional-mediation-dpop-binding"
---

# WebAuthn Conditional Mediation with RFC 9449 DPoP Cryptographic Token Binding

> **Severity**: CRITICAL | **Category**: security
> **Anti-Pattern Warning**: Exchanging WebAuthn passkey assertions for unbound bearer tokens or ambient cookies vulnerable to AiTM replay and exfiltration

## System Constraints
You are a Principal Security Architect enforcing zero-trust identity invariants. You must mandate that all WebAuthn Conditional Mediation passkey authentication flows are cryptographically bound to an RFC 9449 Demonstrating Proof-of-Possession (DPoP) client key. Never allow issuance or acceptance of bearer access tokens, standard cookies without DPoP confirmation (`cnf.jkt`), or unverified WebAuthn assertions.

## Strict Guidelines
- Clients MUST generate or retrieve an asymmetric cryptographic key pair (e.g., ECDSA P-256 / ES256) via the Web Cryptography API (`crypto.subtle`) prior to initiating the conditional mediation assertion exchange.
- Clients MUST execute `navigator.credentials.get({ mediation: 'conditional', ... })` and pass the server-issued challenge verbatim without client-side mutation.
- Authentication endpoints MUST require an RFC 9449 `DPoP` HTTP header containing a signed proof JWT (`typ: 'dpop+jwt'`) matching the target endpoint HTTP method (`htm`) and URI (`htu`).
- Auth servers MUST calculate the JWK SHA-256 Thumbprint (`jkt` per RFC 7638) of the client's DPoP public key and embed it into the issued access token payload under the `cnf.jkt` claim.
- Resource servers MUST verify that every incoming authenticated request includes a valid DPoP proof JWT whose public key thumbprint strictly matches the `cnf.jkt` claim in the presented access token.
- Services MUST enforce DPoP nonces and replay caches with short time-to-live (`iat` jitter tolerance <= 60 seconds) using unique `jti` tracking.
- Components NEVER store private DPoP signing keys in exportable formats or unencrypted web storage (`localStorage` / `sessionStorage`); they MUST be kept in non-extractable CryptoKey handles.

## Bad vs Good Implementations
### Anti-Pattern: Exchanging Passkey Assertion for Unbound Bearer Token

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: The WebAuthn assertion validates the user's presence, but the server returns a standard bearer token. If an attacker intercepts the token via AiTM proxies, cross-site scripting (XSS), or log inspection, the token can be replayed from any client without restriction.
```typescript
import { startAuthentication } from '@simplewebauthn/browser';

// ANTI-PATTERN: Client authenticates with passkey but receives an unbound bearer token
export async function handleConditionalLogin() {
  const optsRes = await fetch('/api/auth/generate-auth-options');
  const options = await optsRes.json();

  // WebAuthn conditional UI
  const assertion = await startAuthentication({
    optionsJSON: options,
    useBrowserAutofill: true,
  });

  // Flaw: Assertion exchanged for standard bearer token without proof of possession
  const verifyRes = await fetch('/api/auth/verify-assertion', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(assertion),
  });

  const { accessToken } = await verifyRes.json();
  // Access token can be stolen via XSS/AiTM and replayed by any client
  localStorage.setItem('access_token', accessToken);
}
```

#### Production Standard (Correct)
> **Standard**: The client generates a non-extractable WebCrypto key pair and produces an RFC 9449 DPoP proof. The server verifies both the WebAuthn assertion and the DPoP proof, binding the token to the key thumbprint (`cnf.jkt`). Replaying the token without the private key fails immediately.
```typescript
import { startAuthentication } from '@simplewebauthn/browser';
import { calculateJwkThumbprint, generateDPoPKeyPair, createDPoPProof } from './crypto-dpop';

// PRODUCTION FIX: Generate non-extractable DPoP key and bind to passkey assertion
export async function handleConditionalLoginWithDPoP() {
  const optsRes = await fetch('/api/auth/generate-auth-options');
  const options = await optsRes.json();

  // 1. Generate non-extractable client key pair for RFC 9449 DPoP
  const dpopKeyPair = await generateDPoPKeyPair();
  const targetUrl = `${window.location.origin}/api/auth/verify-assertion`;
  const httpMethod = 'POST';

  // 2. Perform WebAuthn Conditional Mediation
  const assertion = await startAuthentication({
    optionsJSON: options,
    useBrowserAutofill: true,
  });

  // 3. Generate DPoP Proof signed by ephemeral private key
  const dpopProof = await createDPoPProof(dpopKeyPair, httpMethod, targetUrl);

  // 4. Exchange assertion with DPoP proof in header
  const verifyRes = await fetch(targetUrl, {
    method: httpMethod,
    headers: {
      'Content-Type': 'application/json',
      'DPoP': dpopProof,
    },
    body: JSON.stringify(assertion),
  });

  if (!verifyRes.ok) throw new Error('Assertion failed');

  // Received token contains cnf.jkt matching dpopKeyPair public thumbprint
  const { accessToken } = await verifyRes.json();
  return { accessToken, dpopKeyPair };
}
```

### Server-Side Token Issuance & DPoP Confirmation Binding (`cnf.jkt`)

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: The server verifies the WebAuthn response but creates a bearer JWT lacking a `cnf` (confirmation) claim. Any client in possession of this JWT can access protected APIs regardless of whether it owns the originating device or session key.
```typescript
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
import jwt from 'jsonwebtoken';

// ANTI-PATTERN: Server issues bearer JWT without confirming DPoP binding
export async function verifyAndIssueToken(req: Request) {
  const body = await req.json();
  const verification = await verifyAuthenticationResponse({
    response: body,
    expectedChallenge: req.session.challenge,
    expectedOrigin: 'https://example.com',
    expectedRPID: 'example.com',
    authenticator: req.session.authenticator,
  });

  if (!verification.verified) throw new Error('Unauthorized');

  // Flaw: Unbound bearer token issued
  return jwt.sign({ sub: verification.authenticationInfo.credentialID }, 'secret', {
    expiresIn: '1h',
  });
}
```

#### Production Standard (Correct)
> **Standard**: The server requires an RFC 9449 DPoP proof header along with the WebAuthn response. It validates the proof's method, URI, and signature, derives the SHA-256 thumbprint of the client's public key (`jkt`), and signs the access token with the `cnf: { jkt }` claim. Token theft is mitigated since protected resources mandate proof of possession of the matching private key.
```typescript
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
import { createRemoteJWKSet, jwtVerify, importJWK, calculateJwkThumbprint } from 'jose';
import { SignJWT } from 'jose';
import crypto from 'node:crypto';

// PRODUCTION FIX: Verify WebAuthn + validate DPoP proof + bind cnf.jkt
export async function verifyAndIssueDPoPBoundToken(req: Request, serverNonce: string, expectedChallenge: string) {
  const dpopHeader = req.headers.get('dpop');
  if (!dpopHeader) {
    throw new Error('RFC 9449 DPoP header required');
  }

  // 1. Verify DPoP Proof Structure and Signature
  const [headerB64] = dpopHeader.split('.');
  const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8'));
  if (header.typ !== 'dpop+jwt' || !header.jwk) {
    throw new Error('Invalid DPoP header format');
  }

  const dpopKey = await importJWK(header.jwk, header.alg);
  const { payload } = await jwtVerify(dpopHeader, dpopKey, {
    typ: 'dpop+jwt',
    algorithms: ['ES256', 'EdDSA'],
  });

  if (payload.htm !== req.method || payload.htu !== req.url) {
    throw new Error('DPoP htm/htu mismatch');
  }

  // 2. Verify WebAuthn Assertion
  const body = await req.json();
  const verification = await verifyAuthenticationResponse({
    response: body,
    expectedChallenge,
    expectedOrigin: 'https://example.com',
    expectedRPID: 'example.com',
    authenticator: { credentialPublicKey: Buffer.from('...', 'base64') } as any,
  });

  if (!verification.verified) {
    throw new Error('WebAuthn verification failed');
  }

  // 3. Compute RFC 7638 SHA-256 JWK Thumbprint
  const jkt = await calculateJwkThumbprint(header.jwk, 'sha256');

  // 4. Issue DPoP-Bound Access Token containing cnf.jkt
  const privateKey = crypto.createPrivateKey(process.env.JWT_SIGNING_KEY!);
  return new SignJWT({ sub: verification.authenticationInfo.credentialID })
    .setProtectedHeader({ alg: 'ES256', typ: 'at+jwt' })
    .setIssuedAt()
    .setExpirationTime('15m')
    .setIssuer('https://example.com')
    .setAudience('https://api.example.com')
    .setJti(crypto.randomUUID())
    .claim('cnf', { jkt }) // Immutably bound to DPoP key
    .sign(privateKey);
}
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: WebAuthn Conditional Mediation with RFC 9449 DPoP Cryptographic Token Binding

WebAuthn Conditional Mediation with RFC 9449 DPoP Cryptographic Token Binding defines an industrial-grade behavioral standard for security engineering. Enforces immutable cryptographic binding between WebAuthn conditional mediation (passkey autofill) assertions and RFC 9449 Demonstrating Proof-of-Possession (DPoP) key pairs. Eliminates bearer token replay, session fixation, and adversary-in-the-middle (AiTM) hijacking by anchoring issued session tokens to an ephemeral, client-held private key thumbprint (jkt) verified on every downstream request. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Exchanging WebAuthn passkey assertions for unbound bearer tokens or ambient cookies vulnerable to AiTM replay and exfiltration Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftExchanging WebAuthn passkey assertions for unbound bearer tokens or ambient cookies vulnerable to AiTM replay and exfiltrationCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Clients MUST generate or retrieve an asymmetric cryptographic key pair (e.g., ECDSA P-256 / ES256) via the Web Cryptography API (`crypto.subtle`) prior to initiating the conditional mediation assertion exchange.

Clients MUST generate or retrieve an asymmetric cryptographic key pair (e.g., ECDSA P-256 / ES256) via the Web Cryptography API (`crypto.subtle`) prior to initiating the conditional mediation assertion exchange.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Clients MUST execute `navigator.credentials.get({ mediation

Clients MUST execute `navigator.credentials.get({ mediation: 'conditional', ... })` and pass the server-issued challenge verbatim without client-side mutation.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Authentication endpoints MUST require an RFC 9449 `DPoP` HTTP header containing a signed proof JWT (`typ

Authentication endpoints MUST require an RFC 9449 `DPoP` HTTP header containing a signed proof JWT (`typ: 'dpop+jwt'`) matching the target endpoint HTTP method (`htm`) and URI (`htu`).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Auth servers MUST calculate the JWK SHA-256 Thumbprint (`jkt` per RFC 7638) of the client's DPoP public key and embed it into the issued access token payload under the `cnf.jkt` claim.

Auth servers MUST calculate the JWK SHA-256 Thumbprint (`jkt` per RFC 7638) of the client's DPoP public key and embed it into the issued access token payload under the `cnf.jkt` claim.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Resource servers MUST verify that every incoming authenticated request includes a valid DPoP proof JWT whose public key thumbprint strictly matches the `cnf.jkt` claim in the presented access token.

Resource servers MUST verify that every incoming authenticated request includes a valid DPoP proof JWT whose public key thumbprint strictly matches the `cnf.jkt` claim in the presented access token.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
6

Services MUST enforce DPoP nonces and replay caches with short time-to-live (`iat` jitter tolerance <= 60 seconds) using unique `jti` tracking.

Services MUST enforce DPoP nonces and replay caches with short time-to-live (`iat` jitter tolerance <= 60 seconds) using unique `jti` tracking.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
7

Components NEVER store private DPoP signing keys in exportable formats or unencrypted web storage (`localStorage` / `sessionStorage`)

Components NEVER store private DPoP signing keys in exportable formats or unencrypted web storage (`localStorage` / `sessionStorage`); they MUST be kept in non-extractable CryptoKey handles.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is WebAuthn Conditional Mediation with RFC 9449 DPoP Cryptographic Token Binding critical for AI coding assistants?

Enforces immutable cryptographic binding between WebAuthn conditional mediation (passkey autofill) assertions and RFC 9449 Demonstrating Proof-of-Possession (DPoP) key pairs. Eliminates bearer token replay, session fixation, and adversary-in-the-middle (AiTM) hijacking by anchoring issued session tokens to an ephemeral, client-held private key thumbprint (jkt) verified on every downstream request. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Exchanging WebAuthn passkey assertions for unbound bearer tokens or ambient cookies vulnerable to AiTM replay and exfiltration) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

1 Rule in Stack

WebAuthn