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

Enforce OAuth 2.1 RFC 9449 Demonstrating Proof-of-Possession (DPoP) Token Binding

Mandates asymmetric cryptographic proof-of-possession (DPoP) verification on all incoming OAuth 2.1 access and refresh token flows. Prevents token replay and exfiltration attacks by strictly enforcing JWK thumbprint binding (cnf.jkt), access token hashing (ath), HTTP method/URI binding (htm/htu), and server-provided nonce validation.

Prohibited Anti-Pattern: Accepting Unconstrained Bearer Tokens Without Asymmetric Proof-of-Possession and Thumbprint Binding

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Enforce OAuth 2.1 RFC 9449 Demonstrating Proof-of-Possession (DPoP) Token Binding - Mandates asymmetric cryptographic proof-of-possession (DPoP) verification on all incoming OAuth 2.1 access and refresh token flows. Prevents token replay and exfiltration attacks by strictly enforcing JWK thumbprint binding (cnf.jkt), access token hashing (ath), HTTP method/URI binding (htm/htu), and server-provided nonce validation.
globs: src/auth/**/*.{ts,tsx}, src/middleware/**/*.{ts,tsx}, src/gateway/**/*.{ts,tsx}, src/api/**/*.{ts,tsx}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/oauth21-rfc9449-dpop-sender-constrained-tokens"
---

# Enforce OAuth 2.1 RFC 9449 Demonstrating Proof-of-Possession (DPoP) Token Binding

> **Severity**: CRITICAL | **Category**: security
> **Anti-Pattern Warning**: Accepting Unconstrained Bearer Tokens Without Asymmetric Proof-of-Possession and Thumbprint Binding

## System Constraints
You are a Principal Security Architect enforcing RFC 9449 (DPoP) token binding across all OAuth 2.1 authentication services and resource servers. You must reject plain Bearer tokens in favor of sender-constrained DPoP tokens. Every request must validate asymmetric proof signatures, access token hashes (ath), target URI/method bindings (htu/htm), replay protection (jti cache), and cryptographic thumbprint alignment (cnf.jkt).

## Strict Guidelines
- Engineers MUST ALWAYS verify that authorization schemes use 'DPoP' instead of unconstrained 'Bearer' for sensitive API routes.
- Services MUST validate that the DPoP proof JWT header contains 'typ': 'dpop+jwt' and an embedded ephemeral public JWK without private key parameters.
- Engineers MUST compute the SHA-256 thumbprint of the embedded JWK and verify equality against the token's 'cnf.jkt' confirmation claim.
- Resource Servers MUST compute the base64url-encoded SHA-256 hash of the presented access token and assert strict equality with the DPoP proof's 'ath' claim.
- Services MUST assert that 'htm' matches the request HTTP method and 'htu' matches the normalized target request URI without query parameters or fragments.
- Engineers MUST implement strict replay defense by asserting unique 'jti' identifiers within a bounded timestamp window (max ±60 seconds) or validating server-enforced 'DPoP-Nonce' headers.
- Components NEVER store private DPoP signing keys in local storage or unencrypted persistent memory; public clients MUST utilize WebCrypto API with non-extractable CryptoKeyPair handles.

## Bad vs Good Implementations
### Resource Server DPoP Proof & Token Binding Verification Middleware

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Accepts standard Bearer tokens without cryptographic binding to the sender. Stolen tokens from network eavesdropping, log leaks, or XSS can be replayed without restriction by arbitrary third parties.
```typescript
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export function authenticateRequest(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers['authorization'];
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or invalid token scheme' });
  }

  const token = authHeader.substring(7);
  try {
    // VULNERABLE: Accepts unconstrained bearer token without proof-of-possession.
    // An exfiltrated token can be replayed from any client IP or unauthorized origin.
    const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY!);
    (req as any).user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}
```

#### Production Standard (Correct)
> **Standard**: Implements full RFC 9449 compliance: validates DPoP authorization scheme, matches JWK thumbprint (jkt) against access token confirmation claims (cnf), verifies proof signature, prevents replay attacks via jti cache, asserts matching HTTP method/URI (htm/htu), and calculates SHA-256 access token hash (ath) with constant-time equality check.
```typescript
import { Request, Response, NextFunction } from 'express';
import { createHash, timingSafeEqual } from 'node:crypto';
import * as jose from 'jose';

interface ReplayCache {
  has(jti: string): boolean;
  add(jti: string, ttlSeconds: number): void;
}

declare const replayCache: ReplayCache;

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

  if (!authHeader || !authHeader.startsWith('DPoP ')) {
    res.set('WWW-Authenticate', 'DPoP error="invalid_token", error_description="DPoP scheme required"');
    res.status(401).json({ error: 'invalid_dpop_scheme' });
    return;
  }

  if (typeof dpopHeader !== 'string') {
    res.set('WWW-Authenticate', 'DPoP error="invalid_dpop_proof"');
    res.status(401).json({ error: 'missing_dpop_proof' });
    return;
  }

  const rawAccessToken = authHeader.substring(5).trim();

  try {
    // 1. Verify access token signature and extract cnf.jkt
    const jwks = jose.createRemoteJWKSet(new URL(process.env.AUTH_JWKS_URI!));
    const { payload: tokenClaims } = await jose.jwtVerify(rawAccessToken, jwks, {
      issuer: process.env.AUTH_ISSUER,
      audience: process.env.AUTH_AUDIENCE,
    });

    const expectedJkt = (tokenClaims.cnf as { jkt?: string } | undefined)?.jkt;
    if (!expectedJkt) {
      res.status(401).json({ error: 'token_not_dpop_bound' });
      return;
    }

    // 2. Decode DPoP proof protected header
    const { protectedHeader } = jose.decodeProtectedHeader(dpopHeader);
    if (protectedHeader.typ !== 'dpop+jwt' || !protectedHeader.jwk) {
      res.status(401).json({ error: 'invalid_dpop_header' });
      return;
    }

    // 3. Import public key from proof header and compute thumbprint
    const proofPublicKey = await jose.importJWK(protectedHeader.jwk, protectedHeader.alg);
    const actualJkt = await jose.calculateJwkThumbprint(protectedHeader.jwk, 'sha256');

    if (actualJkt !== expectedJkt) {
      res.status(401).json({ error: 'jkt_mismatch' });
      return;
    }

    // 4. Verify DPoP proof signature, lifetime, and replay prevention
    const { payload: dpopClaims } = await jose.jwtVerify(dpopHeader, proofPublicKey, {
      maxTokenAge: '60s',
      clockTolerance: '5s',
    });

    const jti = dpopClaims.jti as string;
    if (!jti || replayCache.has(jti)) {
      res.status(401).json({ error: 'dpop_replay_detected' });
      return;
    }
    replayCache.add(jti, 60);

    // 5. Verify HTTP method and URI bindings (htm and htu)
    const currentUri = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`;
    if (dpopClaims.htm !== req.method || dpopClaims.htu !== currentUri) {
      res.status(401).json({ error: 'htm_or_htu_mismatch' });
      return;
    }

    // 6. Verify Access Token Hash (ath)
    const expectedAth = createHash('sha256').update(rawAccessToken).digest('base64url');
    const receivedAth = dpopClaims.ath as string;
    if (!receivedAth || !timingSafeEqual(Buffer.from(expectedAth), Buffer.from(receivedAth))) {
      res.status(401).json({ error: 'ath_mismatch' });
      return;
    }

    (req as any).user = tokenClaims;
    next();
  } catch (error) {
    res.status(401).json({ error: 'dpop_verification_failed' });
  }
}
```

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: Enforce OAuth 2.1 RFC 9449 Demonstrating Proof-of-Possession (DPoP) Token Binding

Enforce OAuth 2.1 RFC 9449 Demonstrating Proof-of-Possession (DPoP) Token Binding defines an industrial-grade behavioral standard for security engineering. Mandates asymmetric cryptographic proof-of-possession (DPoP) verification on all incoming OAuth 2.1 access and refresh token flows. Prevents token replay and exfiltration attacks by strictly enforcing JWK thumbprint binding (cnf.jkt), access token hashing (ath), HTTP method/URI binding (htm/htu), and server-provided nonce validation. 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 Unconstrained Bearer Tokens Without Asymmetric Proof-of-Possession and Thumbprint Binding 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 Unconstrained Bearer Tokens Without Asymmetric Proof-of-Possession and Thumbprint BindingCritical
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

Engineers MUST ALWAYS verify that authorization schemes use 'DPoP' instead of unconstrained 'Bearer' for sensitive API routes.

Engineers MUST ALWAYS verify that authorization schemes use 'DPoP' instead of unconstrained 'Bearer' for sensitive API routes.

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

Services MUST validate that the DPoP proof JWT header contains 'typ'

Services MUST validate that the DPoP proof JWT header contains 'typ': 'dpop+jwt' and an embedded ephemeral public JWK without private key parameters.

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

Engineers MUST compute the SHA-256 thumbprint of the embedded JWK and verify equality against the token's 'cnf.jkt' confirmation claim.

Engineers MUST compute the SHA-256 thumbprint of the embedded JWK and verify equality against the token's 'cnf.jkt' confirmation 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.
4

Resource Servers MUST compute the base64url-encoded SHA-256 hash of the presented access token and assert strict equality with the DPoP proof's 'ath' claim.

Resource Servers MUST compute the base64url-encoded SHA-256 hash of the presented access token and assert strict equality with the DPoP proof's 'ath' 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

Services MUST assert that 'htm' matches the request HTTP method and 'htu' matches the normalized target request URI without query parameters or fragments.

Services MUST assert that 'htm' matches the request HTTP method and 'htu' matches the normalized target request 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.
6

Engineers MUST implement strict replay defense by asserting unique 'jti' identifiers within a bounded timestamp window (max ±60 seconds) or validating server-enforced 'DPoP-Nonce' headers.

Engineers MUST implement strict replay defense by asserting unique 'jti' identifiers within a bounded timestamp window (max ±60 seconds) or validating server-enforced 'DPoP-Nonce' headers.

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 local storage or unencrypted persistent memory

Components NEVER store private DPoP signing keys in local storage or unencrypted persistent memory; public clients MUST utilize WebCrypto API with non-extractable CryptoKeyPair 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 Enforce OAuth 2.1 RFC 9449 Demonstrating Proof-of-Possession (DPoP) Token Binding critical for AI coding assistants?

Mandates asymmetric cryptographic proof-of-possession (DPoP) verification on all incoming OAuth 2.1 access and refresh token flows. Prevents token replay and exfiltration attacks by strictly enforcing JWK thumbprint binding (cnf.jkt), access token hashing (ath), HTTP method/URI binding (htm/htu), and server-provided nonce validation. 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 Unconstrained Bearer Tokens Without Asymmetric Proof-of-Possession and Thumbprint Binding) 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

Enforce