This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
securitycritical PriorityID: oauth2-rfc9449-dpop-sender-constrained-tokens

OAuth 2.0 RFC 9449 DPoP Cryptographic Token Binding Invariant

Enforces strict implementation of OAuth 2.0 Demonstrating Proof-of-Possession (DPoP, RFC 9449) across authorization servers and resource servers. Eliminates credential exfiltration vulnerabilities by cryptographically binding access and refresh tokens to client-held ephemeral asymmetric keys, strictly verifying the ath hash, htm method, htu URI, and jwk thumbprint matching the token's cnf.jkt claim.

Prohibited Anti-Pattern: Accepting DPoP Proofs Without ath Binding, Permitting Bearer Downgrades, or Skipping Proof Replay and URL Normalization Checks

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: OAuth 2.0 RFC 9449 DPoP Cryptographic Token Binding Invariant - Enforces strict implementation of OAuth 2.0 Demonstrating Proof-of-Possession (DPoP, RFC 9449) across authorization servers and resource servers. Eliminates credential exfiltration vulnerabilities by cryptographically binding access and refresh tokens to client-held ephemeral asymmetric keys, strictly verifying the ath hash, htm method, htu URI, and jwk thumbprint matching the token's cnf.jkt claim.
globs: src/**/auth/**/*.{ts,js,mjs}, src/**/middleware/**/*.{ts,js,mjs}, src/**/security/**/*.{ts,js,mjs}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/oauth2-rfc9449-dpop-sender-constrained-tokens"
---

# OAuth 2.0 RFC 9449 DPoP Cryptographic Token Binding Invariant

> **Severity**: CRITICAL | **Category**: security
> **Anti-Pattern Warning**: Accepting DPoP Proofs Without ath Binding, Permitting Bearer Downgrades, or Skipping Proof Replay and URL Normalization Checks

## System Constraints
You are a Principal Security Architect enforcing OAuth 2.0 RFC 9449 DPoP (Demonstrating Proof-of-Possession). You must ensure that bearer token downgrades are impossible, tokens are cryptographically bound to asymmetric client keys via the cnf.jkt claim, and all DPoP proof validations enforce strict htm, htu, ath, jti freshness, and server-directed nonces.

## Strict Guidelines
- Resource servers MUST strictly reject HTTP requests where the authorization scheme is 'Bearer' if the underlying access token contains a 'cnf' (confirmation) claim; requests MUST use the 'DPoP' authorization scheme.
- Verifiers MUST validate that the DPoP Proof JWT header contains 'typ': 'dpop+jwt' and an asymmetric signature algorithm ('alg' must NOT be 'none', symmetric HMAC, or insecure curves).
- Verifiers MUST extract the public key directly from the DPoP JWT header 'jwk' parameter and calculate its RFC 7638 SHA-256 thumbprint (jkt), asserting that it strictly equals the 'cnf.jkt' claim within the validated access token.
- Verifiers MUST validate that the 'htm' claim strictly matches the normalized HTTP request method (e.g., 'POST', 'GET') and 'htu' matches the exact HTTP target URI without query parameters or fragments.
- Verifiers MUST validate that the 'ath' (Access Token Hash) claim in the DPoP proof matches the base64url-encoded SHA-256 digest of the presented access token.
- Verifiers MUST reject DPoP proofs whose 'iat' (issued-at) timestamp is skewed outside a tight window (e.g., +/- 60 seconds) or fails server-provided 'DPoP-Nonce' validation, and MUST enforce single-use of the 'jti' identifier within that validity window to prevent replay attacks.

## Bad vs Good Implementations
### Resource Server DPoP Verification and Token Binding

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Accepts Bearer scheme for DPoP tokens, fails to cryptographically verify the DPoP signature, skips validation of the 'ath' claim (allowing proof detachment and replay against unauthorized tokens), fails to check 'htm' and 'htu' constraints, and ignores RFC 7638 JWK thumbprint comparison to the access token's 'cnf.jkt'.
```typescript
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

// INSECURE: Accepts DPoP header without validating 'ath', 'htm', 'htu', or 'cnf.jkt'
export async function verifyDPoPMiddleware(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  const dpopHeader = req.headers.dpop as string;

  if (!authHeader) return res.status(401).send('Missing Authorization');
  
  // Flaw 1: Accepts Bearer token even if client negotiated DPoP
  const token = authHeader.replace(/^(Bearer|DPoP)\s+/i, '');
  const decodedToken = jwt.decode(token) as any;

  if (dpopHeader) {
    // Flaw 2: Decodes proof without checking signature algorithm or typ header
    const proof = jwt.decode(dpopHeader) as any;
    // Flaw 3: Does not bind proof to access token (missing 'ath' validation)
    // Flaw 4: Does not verify JWK thumbprint against token's cnf.jkt claim
    // Flaw 5: Does not verify 'htm' or 'htu', allowing proof reuse across endpoints
    if (proof && proof.jti) {
      req.user = decodedToken;
      return next();
    }
  }

  return res.status(401).send('Invalid DPoP proof');
}
```

#### Production Standard (Correct)
> **Standard**: Implements full RFC 9449 compliance: mandates the 'DPoP' scheme, verifies the 'typ' header and asymmetric key algorithms, calculates the RFC 7638 JWK thumbprint to match against the token's 'cnf.jkt' claim, validates that 'htm' and 'htu' match the incoming request method and normalized URI, enforces 'ath' verification via SHA-256, and prevents replays via temporal limits and jti tracking.
```typescript
import { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
import { importJWK, jwtVerify, calculateJwkThumbprint } from 'jose';

interface TokenPayload {
  sub: string;
  cnf?: { jkt: string };
  [key: string]: unknown;
}

const REPLAY_CACHE = new Set<string>();

function base64UrlSha256(data: string): string {
  return crypto.createHash('sha256').update(data, 'ascii').digest('base64url');
}

export async function verifyDPoPMiddleware(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  const dpopHeader = req.headers['dpop'];

  if (!authHeader || typeof dpopHeader !== 'string') {
    return res.status(401).json({ error: 'invalid_token', message: 'Missing DPoP proof or Authorization header' });
  }

  // Invariant 1: Enforce DPoP auth scheme
  const [scheme, accessToken] = authHeader.split(' ');
  if (scheme !== 'DPoP' || !accessToken) {
    return res.status(401).json({ error: 'invalid_token', message: 'Authorization scheme must be DPoP' });
  }

  try {
    // Assume verifyAccessToken validates JWT signature against authorization server keys
    const tokenPayload = await verifyAccessToken(accessToken);
    if (!tokenPayload.cnf?.jkt) {
      return res.status(401).json({ error: 'invalid_token', message: 'Token missing required cnf.jkt binding' });
    }

    // Parse unverified header to extract JWK
    const [headerB64] = dpopHeader.split('.');
    const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString('utf8'));

    if (header.typ !== 'dpop+jwt' || !header.jwk || header.alg === 'none' || header.alg.startsWith('HS')) {
      return res.status(400).json({ error: 'invalid_dpop_proof', message: 'Invalid proof header or algorithm' });
    }

    // Invariant 2: Cryptographic key binding verification via RFC 7638 thumbprint
    const computedJkt = await calculateJwkThumbprint(header.jwk, 'sha256');
    if (computedJkt !== tokenPayload.cnf.jkt) {
      return res.status(401).json({ error: 'invalid_token', message: 'JWK thumbprint mismatch with cnf.jkt' });
    }

    // Invariant 3: Verify proof signature using embedded public key
    const publicKey = await importJWK(header.jwk, header.alg);
    const { payload: proofPayload } = await jwtVerify(dpopHeader, publicKey, {
      algorithms: ['ES256', 'ES384', 'ES512', 'PS256', 'EdDSA'],
      maxTokenAge: '60s'
    });

    // Invariant 4: Verify htm and htu (normalized target URI without query or fragment)
    const normalizedHtu = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`;
    if (proofPayload.htm !== req.method.toUpperCase() || proofPayload.htu !== normalizedHtu) {
      return res.status(400).json({ error: 'invalid_dpop_proof', message: 'HTTP method or target URI mismatch' });
    }

    // Invariant 5: Verify Access Token Hash (ath)
    const expectedAth = base64UrlSha256(accessToken);
    if (proofPayload.ath !== expectedAth) {
      return res.status(400).json({ error: 'invalid_dpop_proof', message: 'ath claim does not match access token' });
    }

    // Invariant 6: Anti-replay via jti tracking
    const jti = proofPayload.jti as string;
    if (!jti || REPLAY_CACHE.has(jti)) {
      return res.status(400).json({ error: 'invalid_dpop_proof', message: 'DPoP jti replayed or missing' });
    }
    REPLAY_CACHE.add(jti);
    setTimeout(() => REPLAY_CACHE.delete(jti), 60_000);

    req.user = tokenPayload;
    return next();
  } catch (err) {
    return res.status(401).json({ error: 'invalid_dpop_proof', message: 'DPoP validation failed' });
  }
}

async function verifyAccessToken(token: string): Promise<TokenPayload> {
  // Implementation verifying access token signature from AS
  return JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
}
```

Recommended Agent Skills (askills.dev)

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

Architectural Specification & Invariant Rationale

Architectural Invariant Design: OAuth 2.0 RFC 9449 DPoP Cryptographic Token Binding Invariant

OAuth 2.0 RFC 9449 DPoP Cryptographic Token Binding Invariant defines an industrial-grade behavioral standard for security engineering. Enforces strict implementation of OAuth 2.0 Demonstrating Proof-of-Possession (DPoP, RFC 9449) across authorization servers and resource servers. Eliminates credential exfiltration vulnerabilities by cryptographically binding access and refresh tokens to client-held ephemeral asymmetric keys, strictly verifying the ath hash, htm method, htu URI, and jwk thumbprint matching the token's cnf.jkt claim. 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: Accepting DPoP Proofs Without ath Binding, Permitting Bearer Downgrades, or Skipping Proof Replay and URL Normalization Checks 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 DriftAccepting DPoP Proofs Without ath Binding, Permitting Bearer Downgrades, or Skipping Proof Replay and URL Normalization ChecksCritical
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

Resource servers MUST strictly reject HTTP requests where the authorization scheme is 'Bearer' if the underlying access token contains a 'cnf' (confirmation) claim

Resource servers MUST strictly reject HTTP requests where the authorization scheme is 'Bearer' if the underlying access token contains a 'cnf' (confirmation) claim; requests MUST use the 'DPoP' authorization scheme.

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

Verifiers MUST validate that the DPoP Proof JWT header contains 'typ'

Verifiers MUST validate that the DPoP Proof JWT header contains 'typ': 'dpop+jwt' and an asymmetric signature algorithm ('alg' must NOT be 'none', symmetric HMAC, or insecure curves).

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

Verifiers MUST extract the public key directly from the DPoP JWT header 'jwk' parameter and calculate its RFC 7638 SHA-256 thumbprint (jkt), asserting that it strictly equals the 'cnf.jkt' claim within the validated access token.

Verifiers MUST extract the public key directly from the DPoP JWT header 'jwk' parameter and calculate its RFC 7638 SHA-256 thumbprint (jkt), asserting that it strictly equals the 'cnf.jkt' claim within the validated 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.
4

Verifiers MUST validate that the 'htm' claim strictly matches the normalized HTTP request method (e.g., 'POST', 'GET') and 'htu' matches the exact HTTP target URI without query parameters or fragments.

Verifiers MUST validate that the 'htm' claim strictly matches the normalized HTTP request method (e.g., 'POST', 'GET') and 'htu' matches the exact HTTP target URI without query parameters or fragments.

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

Verifiers MUST validate that the 'ath' (Access Token Hash) claim in the DPoP proof matches the base64url-encoded SHA-256 digest of the presented access token.

Verifiers MUST validate that the 'ath' (Access Token Hash) claim in the DPoP proof matches the base64url-encoded SHA-256 digest of 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

Verifiers MUST reject DPoP proofs whose 'iat' (issued-at) timestamp is skewed outside a tight window (e.g., +/- 60 seconds) or fails server-provided 'DPoP-Nonce' validation, and MUST enforce single-use of the 'jti' identifier within that validity window to prevent replay attacks.

Verifiers MUST reject DPoP proofs whose 'iat' (issued-at) timestamp is skewed outside a tight window (e.g., +/- 60 seconds) or fails server-provided 'DPoP-Nonce' validation, and MUST enforce single-use of the 'jti' identifier within that validity window to prevent replay attacks.

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 OAuth 2.0 RFC 9449 DPoP Cryptographic Token Binding Invariant critical for AI coding assistants?

Enforces strict implementation of OAuth 2.0 Demonstrating Proof-of-Possession (DPoP, RFC 9449) across authorization servers and resource servers. Eliminates credential exfiltration vulnerabilities by cryptographically binding access and refresh tokens to client-held ephemeral asymmetric keys, strictly verifying the ath hash, htm method, htu URI, and jwk thumbprint matching the token's cnf.jkt claim. 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 (Accepting DPoP Proofs Without ath Binding, Permitting Bearer Downgrades, or Skipping Proof Replay and URL Normalization Checks) 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

OAuth