This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
securitycritical PriorityID: webauthn-conditional-ui-replay-guardrail-standard

WebAuthn Conditional UI Mediation and Replay-Resistant Challenge Standard

Enforces cryptographic replay resistance and atomic invalidation for WebAuthn autofill (Conditional Mediation) flows. Prevents credential stuffing, passkey enumeration, and token replay attacks by strictly binding autofill credentials to single-use, server-anchored ephemeral challenges tied to an authenticated session state.

Prohibited Anti-Pattern: Unbound Ephemeral Challenges and Static Re-used Nonces in Conditional Autofill WebAuthn Requests

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: WebAuthn Conditional UI Mediation and Replay-Resistant Challenge Standard - Enforces cryptographic replay resistance and atomic invalidation for WebAuthn autofill (Conditional Mediation) flows. Prevents credential stuffing, passkey enumeration, and token replay attacks by strictly binding autofill credentials to single-use, server-anchored ephemeral challenges tied to an authenticated session state.
globs: src/auth/**/*.{ts,tsx}, src/api/auth/**/*.{ts,js}, src/lib/webauthn/**/*.{ts,js}, src/controllers/auth/**/*.{ts,js}, src/components/auth/**/*.{tsx,jsx}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/webauthn-conditional-ui-replay-guardrail-standard"
---

# WebAuthn Conditional UI Mediation and Replay-Resistant Challenge Standard

> **Severity**: CRITICAL | **Category**: security
> **Anti-Pattern Warning**: Unbound Ephemeral Challenges and Static Re-used Nonces in Conditional Autofill WebAuthn Requests

## System Constraints
You are a Principal Security Architect enforcing the WebAuthn Conditional UI Mediation and Replay-Resistant Challenge Standard. You must mandate that all conditional mediation requests use cryptographically random, ephemeral challenges generated server-side, stored with strict TTL and atomic-read deletion (consume-on-verify), and cryptographically verified against clientDataJSON origin and RP ID.

## Strict Guidelines
- Engineers MUST ALWAYS generate WebAuthn challenges on the server using a cryptographically secure pseudorandom number generator (CSPRNG) with at least 32 bytes of entropy.
- Engineers MUST ALWAYS store the generated challenge in an ephemeral, session-bound state store (e.g., Redis or encrypted session cookie) with an absolute TTL not exceeding 120 seconds.
- Engineers MUST ALWAYS delete or consume the challenge immediately upon its first verification attempt (atomic read-and-delete) to prevent replay attacks regardless of whether verification succeeds or fails.
- Engineers MUST NOT allow client-side generated challenges or static fallback challenges under any conditional UI mediation circumstances.
- Services MUST verify that `clientDataJSON.type` strictly equals `'webauthn.get'`, that `clientDataJSON.origin` matches the fully qualified expected RP origin, and that `clientDataJSON.challenge` base64url matches the stored challenge.
- Client components MUST invoke `navigator.credentials.get({ mediation: 'conditional', ... })` with an `AbortController` signal to clean up pending autofill mediation loops on unmount or form submission.

## Bad vs Good Implementations
### Server-Side Challenge Issuance and Atomic Consumption

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Uses a static/shared challenge that is never atomically consumed or tied to an ephemeral session. An attacker intercepting an assertion can replay it indefinitely, and passkey enumeration can be performed.
```typescript
import { Request, Response } from 'express';

// Flawed: Global static or non-expiring challenge prone to replay attacks
const activeChallenges = new Map<string, string>();

export async function getConditionalOptions(req: Request, res: Response) {
  const challenge = 'static-challenge-for-conditional-ui-12345';
  return res.json({
    publicKey: {
      challenge: Buffer.from(challenge).toString('base64url'),
      rpId: 'example.com',
      userVerification: 'preferred'
    }
  });
}

export async function verifyAssertion(req: Request, res: Response) {
  const { credential } = req.body;
  const clientData = JSON.parse(Buffer.from(credential.response.clientDataJSON, 'base64url').toString('utf8'));
  
  // Flawed: Challenge is never invalidated, enabling unlimited replay attacks
  if (clientData.challenge !== Buffer.from('static-challenge-for-conditional-ui-12345').toString('base64url')) {
    return res.status(401).json({ error: 'Invalid challenge' });
  }
  return res.json({ success: true });
}
```

#### Production Standard (Correct)
> **Standard**: Generates a 32-byte CSPRNG challenge per session with a 60-second TTL. Consumes the challenge atomically via `redis.getdel` before assertion validation, guaranteeing zero challenge reuse.
```typescript
import { Request, Response } from 'express';
import crypto from 'node:crypto';
import { redis } from '../lib/redis';
import { verifyAuthenticationResponse } from '@simplewebauthn/server';

const CHALLENGE_TTL_SECONDS = 60;

export async function getConditionalOptions(req: Request, res: Response) {
  const sessionId = req.session.id;
  const challengeBytes = crypto.randomBytes(32);
  const challenge = challengeBytes.toString('base64url');

  // Invariant: Ephemeral storage with strict TTL bound to authenticated session context
  await redis.set(`webauthn:challenge:${sessionId}`, challenge, 'EX', CHALLENGE_TTL_SECONDS);

  return res.json({
    publicKey: {
      challenge,
      rpId: process.env.RP_ID!,
      timeout: CHALLENGE_TTL_SECONDS * 1000,
      userVerification: 'preferred'
    }
  });
}

export async function verifyAssertion(req: Request, res: Response) {
  const sessionId = req.session.id;
  const key = `webauthn:challenge:${sessionId}`;

  // Invariant: Atomic read-and-delete prevents challenge reuse regardless of outcome
  const storedChallenge = await redis.getdel(key);
  if (!storedChallenge) {
    return res.status(400).json({ error: 'Challenge expired or already consumed' });
  }

  const verification = await verifyAuthenticationResponse({
    response: req.body.credential,
    expectedChallenge: storedChallenge,
    expectedOrigin: process.env.EXPECTED_ORIGIN!,
    expectedRPID: process.env.RP_ID!,
    authenticator: await getAuthenticatorForUser(req.body.credential.id),
    requireUserVerification: false
  });

  if (!verification.verified) {
    return res.status(401).json({ error: 'WebAuthn assertion verification failed' });
  }

  return res.json({ verified: true });
}
```

### Client-Side Conditional UI Mediation Lifecycle Management

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Fails to check if conditional mediation is supported, does not wire an AbortController causing hung credential requests across route transitions, and generates an invalid non-random client challenge.
```typescript
import React, { useEffect } from 'react';

export function LoginForm() {
  useEffect(() => {
    // Flawed: Missing feature detection, unhandled abort, potential UI freeze
    navigator.credentials.get({
      mediation: 'conditional',
      publicKey: {
        challenge: new Uint8Array([1, 2, 3, 4]),
        rpId: window.location.hostname
      }
    } as any).then((cred) => {
      fetch('/api/auth/verify', { method: 'POST', body: JSON.stringify(cred) });
    });
  }, []);

  return <input type="text" name="username" autoComplete="username webauthn" />;
}
```

#### Production Standard (Correct)
> **Standard**: Enforces feature-detection for conditional mediation, retrieves an ephemeral server challenge, binds execution to an AbortController to prevent dangling requests on unmount, and gracefully handles abort scenarios.
```typescript
import React, { useEffect } from 'react';

export function LoginForm() {
  useEffect(() => {
    const controller = new AbortController();

    async function initAutofill() {
      if (!window.PublicKeyCredential || !PublicKeyCredential.isConditionalMediationAvailable) {
        return;
      }
      const available = await PublicKeyCredential.isConditionalMediationAvailable();
      if (!available || controller.signal.aborted) {
        return;
      }

      const res = await fetch('/api/auth/conditional-options', { signal: controller.signal });
      const options = await res.json();

      const credential = await navigator.credentials.get({
        mediation: 'conditional',
        signal: controller.signal,
        publicKey: {
          ...options.publicKey,
          challenge: Uint8Array.from(atob(options.publicKey.challenge.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0))
        }
      });

      if (credential) {
        await fetch('/api/auth/verify', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ credential })
        });
      }
    }

    initAutofill().catch((err) => {
      if (err.name !== 'AbortError') console.error('Conditional WebAuthn error', err);
    });

    return () => controller.abort();
  }, []);

  return <input type="text" name="username" autoComplete="username webauthn" />;
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: WebAuthn Conditional UI Mediation and Replay-Resistant Challenge Standard

WebAuthn Conditional UI Mediation and Replay-Resistant Challenge Standard defines an industrial-grade behavioral standard for security engineering. Enforces cryptographic replay resistance and atomic invalidation for WebAuthn autofill (Conditional Mediation) flows. Prevents credential stuffing, passkey enumeration, and token replay attacks by strictly binding autofill credentials to single-use, server-anchored ephemeral challenges tied to an authenticated session state. 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: Unbound Ephemeral Challenges and Static Re-used Nonces in Conditional Autofill WebAuthn Requests 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 DriftUnbound Ephemeral Challenges and Static Re-used Nonces in Conditional Autofill WebAuthn RequestsCritical
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 generate WebAuthn challenges on the server using a cryptographically secure pseudorandom number generator (CSPRNG) with at least 32 bytes of entropy.

Engineers MUST ALWAYS generate WebAuthn challenges on the server using a cryptographically secure pseudorandom number generator (CSPRNG) with at least 32 bytes of entropy.

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 store the generated challenge in an ephemeral, session-bound state store (e.g., Redis or encrypted session cookie) with an absolute TTL not exceeding 120 seconds.

Engineers MUST ALWAYS store the generated challenge in an ephemeral, session-bound state store (e.g., Redis or encrypted session cookie) with an absolute TTL not exceeding 120 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.
3

Engineers MUST ALWAYS delete or consume the challenge immediately upon its first verification attempt (atomic read-and-delete) to prevent replay attacks regardless of whether verification succeeds or fails.

Engineers MUST ALWAYS delete or consume the challenge immediately upon its first verification attempt (atomic read-and-delete) to prevent replay attacks regardless of whether verification succeeds or fails.

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 NOT allow client-side generated challenges or static fallback challenges under any conditional UI mediation circumstances.

Engineers MUST NOT allow client-side generated challenges or static fallback challenges under any conditional UI mediation circumstances.

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 verify that `clientDataJSON.type` strictly equals `'webauthn.get'`, that `clientDataJSON.origin` matches the fully qualified expected RP origin, and that `clientDataJSON.challenge` base64url matches the stored challenge.

Services MUST verify that `clientDataJSON.type` strictly equals `'webauthn.get'`, that `clientDataJSON.origin` matches the fully qualified expected RP origin, and that `clientDataJSON.challenge` base64url matches the stored challenge.

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

Client components MUST invoke `navigator.credentials.get({ mediation

Client components MUST invoke `navigator.credentials.get({ mediation: 'conditional', ... })` with an `AbortController` signal to clean up pending autofill mediation loops on unmount or form submission.

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 UI Mediation and Replay-Resistant Challenge Standard critical for AI coding assistants?

Enforces cryptographic replay resistance and atomic invalidation for WebAuthn autofill (Conditional Mediation) flows. Prevents credential stuffing, passkey enumeration, and token replay attacks by strictly binding autofill credentials to single-use, server-anchored ephemeral challenges tied to an authenticated session state. 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 (Unbound Ephemeral Challenges and Static Re-used Nonces in Conditional Autofill WebAuthn Requests) 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