This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
securitycritical PriorityID: dpop-rfc9449-gateway-proof-replay-defense

OAuth 2.0 DPoP (RFC 9449) Gateway Proof Replay Defense & Distributed Nonce Invariants

Enforces cryptographic verification of RFC 9449 Demonstrating Proof-of-Possession (DPoP) proof tokens at edge API gateways, binding requests to client public keys and token hash (ath). Mandates distributed, atomic single-use tracking of proof identifiers (jti) and low-latency server-generated nonces to guarantee strict replay defense across clustered gateway instances.

Prohibited Anti-Pattern: Stateless DPoP Proof Validation Omitting Distributed JTI Tracking, URI/Method Binding, or Server-Provided Nonce Verification

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: OAuth 2.0 DPoP (RFC 9449) Gateway Proof Replay Defense & Distributed Nonce Invariants - Enforces cryptographic verification of RFC 9449 Demonstrating Proof-of-Possession (DPoP) proof tokens at edge API gateways, binding requests to client public keys and token hash (ath). Mandates distributed, atomic single-use tracking of proof identifiers (jti) and low-latency server-generated nonces to guarantee strict replay defense across clustered gateway instances.
globs: src/gateway/**/*.{ts,js}, src/middleware/**/*.{ts,js}, src/security/**/*.{ts,js}, src/auth/**/*.{ts,js}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/dpop-rfc9449-gateway-proof-replay-defense"
---

# OAuth 2.0 DPoP (RFC 9449) Gateway Proof Replay Defense & Distributed Nonce Invariants

> **Severity**: CRITICAL | **Category**: security
> **Anti-Pattern Warning**: Stateless DPoP Proof Validation Omitting Distributed JTI Tracking, URI/Method Binding, or Server-Provided Nonce Verification

## System Constraints
You are a Principal Security Architect enforcing OAuth 2.0 DPoP (RFC 9449) replay defense and public key binding at edge and internal API gateways. You must ensure all incoming DPoP proofs validate the public key thumbprint (jkt) against the bearer token's 'cnf.jkt' claim, match exact HTTP method ('htm') and fully qualified URI ('htu'), enforce server-issued high-entropy nonces, and enforce distributed atomic single-use semantics on 'jti' within a low-latency key-value store (e.g., Redis). Reject any request lacking cryptographically bound DPoP validation.

## Strict Guidelines
- Engineers MUST ALWAYS verify that the DPoP proof header JWT contains 'typ' equal to 'dpop+jwt' and an asymmetric key ('jwk') in its header.
- Engineers MUST ALWAYS bind the DPoP proof to the access token by computing the SHA-256 hash of the access token and comparing it against the 'ath' claim in the DPoP proof payload.
- Engineers MUST ALWAYS validate the 'cnf.jkt' (SHA-256 JWK Thumbprint) claim in the access token against the computed thumbprint of the JWK present in the DPoP proof header.
- Engineers MUST ALWAYS perform atomic 'SET NX' operations in a distributed, sub-millisecond cache (e.g., Redis/Valkey) on the DPoP proof's 'jti' with an expiration equal to the allowed proof clock skew tolerance window (max 60 seconds).
- Engineers MUST ALWAYS enforce strict equality between the proof's 'htm'/'htu' claims and the actual incoming HTTP method and canonicalized request URI (excluding query and fragment).
- Services MUST reject replayed 'jti' entries with HTTP 401 Unauthorized using error 'invalid_dpop_proof'.
- Services MUST challenge expired or missing nonces with HTTP 401 Unauthorized, an updated 'DPoP-Nonce' response header, and error 'use_dpop_nonce'.
- Components NEVER accept Bearer tokens without valid DPoP proofs when accessing endpoints protected by sender-constrained policies.

## Bad vs Good Implementations
### RFC 9449 DPoP Gateway Middleware with Atomic Redis JTI Replay Guard

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Decodes the DPoP JWT without cryptographic signature verification, fails to bind the proof to the access token via 'ath', ignores HTTP method and URI checks ('htm'/'htu'), and performs no distributed replay defense on the 'jti', allowing stolen proof replay attacks across all gateway instances.
```typescript
import { Request, Response, NextFunction } from 'express';
import * as jose from 'jose';

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

  if (!dpopHeader || !authHeader) {
    return res.status(401).json({ error: 'missing_tokens' });
  }

  // VULNERABILITY 1: Stateless unverified decode, no signature verification
  const { payload } = jose.decodeJwt(dpopHeader);
  
  // VULNERABILITY 2: No check of htm/htu against request
  // VULNERABILITY 3: No distributed replay check on jti; tokens can be replayed freely
  // VULNERABILITY 4: No thumbprint (jkt) comparison against access token 'cnf'
  if (!payload.jti) {
    return res.status(401).json({ error: 'invalid_proof' });
  }

  return next();
}
```

#### Production Standard (Correct)
> **Standard**: Validates the DPoP signature using the embedded JWK, asserts exact method/URI binding, ensures the 'ath' claim precisely hashes the presented access token, and executes an atomic distributed single-use assertion on 'jti' in Redis, preventing token reuse and replay attacks.
```typescript
import { Request, Response, NextFunction } from 'express';
import * as jose from 'jose';
import { createHash } from 'node:crypto';
import type { Redis } from 'ioredis';

interface DPoPOptions {
  redis: Redis;
  maxClockSkewSec?: number;
  expectedServerNonce?: (req: Request) => Promise<string>;
}

export function createDPoPVerifier(options: DPoPOptions) {
  const maxClockSkewSec = options.maxClockSkewSec ?? 60;

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

    if (typeof dpopHeader !== 'string' || typeof authHeader !== 'string' || !authHeader.startsWith('DPoP ')) {
      res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_token"').json({ error: 'invalid_token' });
      return;
    }

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

    try {
      // 1. Parse header to extract ephemeral public key
      const protectedHeader = jose.decodeProtectedHeader(dpopHeader);
      if (protectedHeader.typ !== 'dpop+jwt' || !protectedHeader.jwk) {
        res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_dpop_proof"').json({ error: 'invalid_dpop_proof' });
        return;
      }

      const clientPublicKey = await jose.importJWK(protectedHeader.jwk, protectedHeader.alg);

      // 2. Cryptographically verify proof signature, clock skew, and lifetime
      const { payload } = await jose.jwtVerify(dpopHeader, clientPublicKey, {
        algorithms: ['ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'EdDSA'],
        clockTolerance: `${maxClockSkewSec}s`,
      });

      // 3. Verify HTTP Method and Target URI bindings
      const canonicalHtu = `${req.protocol}://${req.get('host')}${req.baseUrl}${req.path}`;
      if (payload.htm !== req.method.toUpperCase() || payload.htu !== canonicalHtu) {
        res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_dpop_proof"').json({ error: 'invalid_dpop_proof' });
        return;
      }

      // 4. Verify Access Token Hash binding (ath)
      const expectedAth = createHash('sha256').update(rawAccessToken, 'ascii').digest('base64url');
      if (payload.ath !== expectedAth) {
        res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_dpop_proof"').json({ error: 'invalid_dpop_proof' });
        return;
      }

      // 5. Distributed Replay Defense on 'jti' using atomic SET NX
      const jti = payload.jti;
      if (!jti || typeof jti !== 'string') {
        res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_dpop_proof"').json({ error: 'invalid_dpop_proof' });
        return;
      }

      const replayCacheKey = `dpop:jti:${jti}`;
      const acquired = await options.redis.set(replayCacheKey, '1', 'EX', maxClockSkewSec * 2, 'NX');
      if (!acquired) {
        res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_dpop_proof", error_description="Replayed proof"').json({ error: 'invalid_dpop_proof' });
        return;
      }

      // 6. Bind client JWK thumbprint (jkt) to request for upstream token validation
      const jkt = await jose.calculateJwkThumbprint(protectedHeader.jwk, 'sha256');
      req.app.locals.dpop = { jkt, jti, payload };

      next();
    } catch (err) {
      res.status(401).setHeader('WWW-Authenticate', 'DPoP error="invalid_dpop_proof"').json({ error: 'invalid_dpop_proof' });
    }
  };
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: OAuth 2.0 DPoP (RFC 9449) Gateway Proof Replay Defense & Distributed Nonce Invariants

OAuth 2.0 DPoP (RFC 9449) Gateway Proof Replay Defense & Distributed Nonce Invariants defines an industrial-grade behavioral standard for security engineering. Enforces cryptographic verification of RFC 9449 Demonstrating Proof-of-Possession (DPoP) proof tokens at edge API gateways, binding requests to client public keys and token hash (ath). Mandates distributed, atomic single-use tracking of proof identifiers (jti) and low-latency server-generated nonces to guarantee strict replay defense across clustered gateway instances. 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: Stateless DPoP Proof Validation Omitting Distributed JTI Tracking, URI/Method Binding, or Server-Provided Nonce Verification 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 DriftStateless DPoP Proof Validation Omitting Distributed JTI Tracking, URI/Method Binding, or Server-Provided Nonce VerificationCritical
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 the DPoP proof header JWT contains 'typ' equal to 'dpop+jwt' and an asymmetric key ('jwk') in its header.

Engineers MUST ALWAYS verify that the DPoP proof header JWT contains 'typ' equal to 'dpop+jwt' and an asymmetric key ('jwk') in its header.

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

Engineers MUST ALWAYS bind the DPoP proof to the access token by computing the SHA-256 hash of the access token and comparing it against the 'ath' claim in the DPoP proof payload.

Engineers MUST ALWAYS bind the DPoP proof to the access token by computing the SHA-256 hash of the access token and comparing it against the 'ath' claim in the DPoP proof payload.

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 ALWAYS validate the 'cnf.jkt' (SHA-256 JWK Thumbprint) claim in the access token against the computed thumbprint of the JWK present in the DPoP proof header.

Engineers MUST ALWAYS validate the 'cnf.jkt' (SHA-256 JWK Thumbprint) claim in the access token against the computed thumbprint of the JWK present in the DPoP proof header.

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

Engineers MUST ALWAYS perform atomic 'SET NX' operations in a distributed, sub-millisecond cache (e.g., Redis/Valkey) on the DPoP proof's 'jti' with an expiration equal to the allowed proof clock skew tolerance window (max 60 seconds).

Engineers MUST ALWAYS perform atomic 'SET NX' operations in a distributed, sub-millisecond cache (e.g., Redis/Valkey) on the DPoP proof's 'jti' with an expiration equal to the allowed proof clock skew tolerance window (max 60 seconds).

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

Engineers MUST ALWAYS enforce strict equality between the proof's 'htm'/'htu' claims and the actual incoming HTTP method and canonicalized request URI (excluding query and fragment).

Engineers MUST ALWAYS enforce strict equality between the proof's 'htm'/'htu' claims and the actual incoming HTTP method and canonicalized request URI (excluding query and fragment).

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 reject replayed 'jti' entries with HTTP 401 Unauthorized using error 'invalid_dpop_proof'.

Services MUST reject replayed 'jti' entries with HTTP 401 Unauthorized using error 'invalid_dpop_proof'.

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

Services MUST challenge expired or missing nonces with HTTP 401 Unauthorized, an updated 'DPoP-Nonce' response header, and error 'use_dpop_nonce'.

Services MUST challenge expired or missing nonces with HTTP 401 Unauthorized, an updated 'DPoP-Nonce' response header, and error 'use_dpop_nonce'.

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.
8

Components NEVER accept Bearer tokens without valid DPoP proofs when accessing endpoints protected by sender-constrained policies.

Components NEVER accept Bearer tokens without valid DPoP proofs when accessing endpoints protected by sender-constrained policies.

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 DPoP (RFC 9449) Gateway Proof Replay Defense & Distributed Nonce Invariants critical for AI coding assistants?

Enforces cryptographic verification of RFC 9449 Demonstrating Proof-of-Possession (DPoP) proof tokens at edge API gateways, binding requests to client public keys and token hash (ath). Mandates distributed, atomic single-use tracking of proof identifiers (jti) and low-latency server-generated nonces to guarantee strict replay defense across clustered gateway instances. 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 (Stateless DPoP Proof Validation Omitting Distributed JTI Tracking, URI/Method Binding, or Server-Provided Nonce Verification) 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