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

WebAuthn Conditional UI & Cryptographic Origin-Binding Invariant

Enforces strict WebAuthn Conditional Mediation (passkey autofill) on client interfaces alongside deterministic, immutable origin and Relying Party (RP) ID validation on the server. Guarantees that passkey assertions are cryptographically bound to the canonical, server-configured origin, neutralizing real-time reverse-proxy phishing (AitM) and spoofed domain vectors.

Prohibited Anti-Pattern: Dynamic Origin Validation, Missing Conditional UI Fallbacks, or Trusting Client-Reported Hostnames

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: WebAuthn Conditional UI & Cryptographic Origin-Binding Invariant - Enforces strict WebAuthn Conditional Mediation (passkey autofill) on client interfaces alongside deterministic, immutable origin and Relying Party (RP) ID validation on the server. Guarantees that passkey assertions are cryptographically bound to the canonical, server-configured origin, neutralizing real-time reverse-proxy phishing (AitM) and spoofed domain vectors.
globs: src/**/auth/**/*.{ts,tsx}, src/lib/auth/**/*.{ts,tsx}, src/server/auth/**/*.{ts,tsx}, src/pages/api/auth/**/*.{ts,tsx}, src/app/api/auth/**/*.{ts,tsx}
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/webauthn-conditional-ui-passkey-origin-binding-invariant"
---

# WebAuthn Conditional UI & Cryptographic Origin-Binding Invariant

> **Severity**: CRITICAL | **Category**: security
> **Anti-Pattern Warning**: Dynamic Origin Validation, Missing Conditional UI Fallbacks, or Trusting Client-Reported Hostnames

## System Constraints
You are a Principal Security Architect enforcing the WebAuthn Conditional UI & Cryptographic Origin-Binding Invariant. Passkeys are inherently phishing-resistant only when both ends of the protocol strictly enforce FIDO2/WebAuthn specifications. You must reject any implementation that derives expected origins or RP IDs dynamically from untrusted HTTP request headers (Host, X-Forwarded-Host, or clientDataJSON.origin). Mandate client-side feature detection with PublicKeyCredential.isConditionalMediationAvailable(), canonical input autocomplete attributes, and robust AbortController lifecycle handling for seamless autofill. On the server, mandate cryptographic verification of authenticator data, challenge single-use validation, sign count monotonicity, and strict origin matching against an immutable compile-time or environment-locked canonical allowlist.

## Strict Guidelines
- Engineers MUST ALWAYS verify client support for Conditional UI using window.PublicKeyCredential?.isConditionalMediationAvailable() before invoking navigator.credentials.get with mediation: 'conditional'.
- Engineers MUST configure input fields with autocomplete='username webauthn' to bind standard autofill behaviors to the platform authenticator conditional mediation loop.
- Engineers MUST manage an AbortController instance across client credential queries to cleanly terminate background mediation when users navigate away or manually submit credentials.
- Services MUST NEVER derive expectedOrigin or expectedRPID from incoming request headers (such as Host, X-Forwarded-Host, Origin, or client-supplied payloads).
- Services MUST validate clientDataJSON.origin against an immutable, strictly configured allowlist of canonical origins using exact, case-sensitive matching.
- Services MUST verify clientDataJSON.type === 'webauthn.get' for authentication assertions and 'webauthn.create' for registration ceremonies.
- Services MUST enforce cryptographic signature verification using the user's stored public key, and MUST verify that the SHA-256 hash of the expected RP ID strictly equals rpIdHash in authenticatorData.
- Services MUST verify the single-use cryptographic challenge stored in the server session or cache, deleting it immediately upon verification to prevent replay attacks.
- Services MUST assert that the user present (UP) bit is set in the authenticatorData flags, and check that the signature counter (signCount) strictly increments when non-zero.

## Bad vs Good Implementations
### Client-Side: Conditional UI Autofill with Abort Management vs Naive Unmediated Request

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Attempting to invoke navigator.credentials.get on mount without mediation: 'conditional' triggers an immediate, intrusive browser modal rather than seamless autofill. Furthermore, relying on window.location.hostname directly, omitting AbortController cleanup, and missing webauthn autocomplete tokens fails to adhere to WebAuthn Conditional UI standards.
```typescript
import React, { useEffect } from 'react';

// VULNERABLE: Direct modal credential request on load without feature detection or conditional mediation
export function LoginForm() {
  useEffect(() => {
    async function triggerPasskey() {
      const challengeRes = await fetch('/api/auth/challenge');
      const { challenge } = await challengeRes.json();

      // BAD: Missing mediation: 'conditional', locks browser modal immediately, breaking user experience
      // BAD: Missing AbortController, leading to unhandled promise leaks on unmount
      const credential = await navigator.credentials.get({
        publicKey: {
          challenge: Uint8Array.from(atob(challenge), c => c.charCodeAt(0)),
          rpId: window.location.hostname,
          userVerification: 'preferred'
        }
      });

      await fetch('/api/auth/verify', {
        method: 'POST',
        body: JSON.stringify(credential)
      });
    }
    triggerPasskey();
  }, []);

  // BAD: Missing autocomplete='username webauthn' for conditional UI integration
  return (
    <form>
      <input type="text" name="username" autoComplete="username" />
      <button type="submit">Sign In</button>
    </form>
  );
}
```

#### Production Standard (Correct)
> **Standard**: Implements proper Conditional UI with feature detection (PublicKeyCredential.isConditionalMediationAvailable), adds 'username webauthn' to the input autocomplete attribute, coordinates cancellation via AbortController, and transmits standardized base64 credential data safely.
```typescript
import React, { useEffect, useRef } from 'react';

export function LoginForm() {
  const abortControllerRef = useRef<AbortController | null>(null);

  useEffect(() => {
    let isMounted = true;

    async function initConditionalPasskey() {
      const isCUI = await window.PublicKeyCredential?.isConditionalMediationAvailable?.();
      if (!isCUI || !isMounted) return;

      abortControllerRef.current = new AbortController();

      try {
        const challengeRes = await fetch('/api/auth/assertion-options');
        if (!challengeRes.ok) return;
        const options = await challengeRes.json();

        const credential = await navigator.credentials.get({
          publicKey: {
            challenge: Uint8Array.from(atob(options.challenge), c => c.charCodeAt(0)),
            rpId: options.rpId,
            userVerification: 'preferred',
            allowCredentials: options.allowCredentials ?? []
          },
          mediation: 'conditional',
          signal: abortControllerRef.current.signal
        }) as PublicKeyCredential | null;

        if (credential && isMounted) {
          await fetch('/api/auth/assertion-verify', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              id: credential.id,
              rawId: btoa(String.fromCharCode(...new Uint8Array(credential.rawId))),
              response: {
                authenticatorData: btoa(String.fromCharCode(...new Uint8Array((credential.response as AuthenticatorAssertionResponse).authenticatorData))),
                clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(credential.response.clientDataJSON))),
                signature: btoa(String.fromCharCode(...new Uint8Array((credential.response as AuthenticatorAssertionResponse).signature))),
                userHandle: (credential.response as AuthenticatorAssertionResponse).userHandle
                  ? btoa(String.fromCharCode(...new Uint8Array((credential.response as AuthenticatorAssertionResponse).userHandle!)))
                  : null
              }
            })
          });
          window.location.assign('/dashboard');
        }
      } catch (err: unknown) {
        if (err instanceof DOMException && err.name === 'AbortError') return;
        console.error('Passkey conditional UI error:', err);
      }
    }

    initConditionalPasskey();

    return () => {
      isMounted = false;
      abortControllerRef.current?.abort();
    };
  }, []);

  return (
    <form onSubmit={(e) => { e.preventDefault(); abortControllerRef.current?.abort(); }}>
      <input
        type="text"
        name="username"
        id="username"
        autoComplete="username webauthn"
        placeholder="Username or Passkey autofill"
      />
      <button type="submit">Sign In</button>
    </form>
  );
}
```

### Server-Side: Strict RP ID and Origin Verification vs Reverse-Proxy Vulnerable Logic

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Trusting the Host header or verifying origins dynamically via string matching (includes) allows reverse proxies (e.g., Modlishka, Evilginx) to proxy authentication ceremonies and accept signatures produced for the phishing origin without detection.
```typescript
import { NextRequest, NextResponse } from 'next/server';

// VULNERABLE: Derives origin and RP ID dynamically from HTTP headers
export async function POST(req: NextRequest) {
  const body = await req.json();
  const clientData = JSON.parse(Buffer.from(body.response.clientDataJSON, 'base64').toString('utf8'));

  // CRITICAL FLAW: Trusting Host header or clientDataJSON.origin facilitates AitM reverse proxy bypass
  // Attacker reverse proxy at 'https://login.evil-phish.com' sets clientData.origin to itself,
  // and req.headers.get('host') matches the evil proxy host.
  const incomingHost = req.headers.get('host');
  if (clientData.origin.includes(incomingHost)) {
    // Bypasses true origin check! Validates rogue domain.
  }

  return NextResponse.json({ authenticated: true });
}
```

#### Production Standard (Correct)
> **Standard**: Enforces strict canonical RP ID and origin matching using an immutable Set, performs constant-time challenge comparison with immediate invalidation, validates the WebAuthn ceremony type, and verifies the SHA-256 hash of the RP ID against the authenticatorData payload.
```typescript
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'node:crypto';

// SECURE: Strict canonical values configured in immutable environment variables
const CANONICAL_RP_ID = process.env.AUTH_RP_ID ?? 'auth.production.domain';
const ALLOWED_ORIGINS = new Set([
  'https://auth.production.domain',
  'https://production.domain'
]);

interface StoredSession {
  expectedChallenge: string;
  userId: string;
}

export async function POST(req: NextRequest) {
  const body = await req.json();
  const { clientDataJSON, authenticatorData } = body.response;

  const clientDataParsed = JSON.parse(Buffer.from(clientDataJSON, 'base64').toString('utf8'));

  // 1. Strict ceremony type validation
  if (clientDataParsed.type !== 'webauthn.get') {
    return NextResponse.json({ error: 'Invalid ceremony type' }, { status: 400 });
  }

  // 2. Strict origin validation against immutable allowlist
  if (!ALLOWED_ORIGINS.has(clientDataParsed.origin)) {
    return NextResponse.json({ error: 'Origin mismatch: Untrusted domain' }, { status: 403 });
  }

  // 3. Challenge consumption & single-use eviction
  const session = await getSession(req); // e.g., session store lookup
  if (!session || !crypto.timingSafeEqual(Buffer.from(clientDataParsed.challenge), Buffer.from(session.expectedChallenge))) {
    return NextResponse.json({ error: 'Invalid challenge' }, { status: 400 });
  }
  await consumeChallenge(session.userId); // Invalidate challenge immediately

  // 4. Cryptographic RP ID Hash Verification from authenticatorData
  const authDataBuffer = Buffer.from(authenticatorData, 'base64');
  const rpIdHashInAuthData = authDataBuffer.subarray(0, 32);
  const expectedRpIdHash = crypto.createHash('sha256').update(CANONICAL_RP_ID).digest();

  if (!crypto.timingSafeEqual(rpIdHashInAuthData, expectedRpIdHash)) {
    return NextResponse.json({ error: 'RP ID Hash verification failed' }, { status: 403 });
  }

  // 5. Flag assertions: User Presence (bit 0)
  const flags = authDataBuffer[32];
  const userPresent = (flags & 0x01) !== 0;
  if (!userPresent) {
    return NextResponse.json({ error: 'User Presence flag required' }, { status: 400 });
  }

  return NextResponse.json({ success: true });
}

async function getSession(_req: NextRequest): Promise<StoredSession | null> {
  return { expectedChallenge: 'stored_b64_challenge', userId: 'usr_123' };
}
async function consumeChallenge(_userId: string): Promise<void> {}
```

Recommended MCP Servers (mcp-serverlist.com)

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

Architectural Specification & Invariant Rationale

Architectural Invariant Design: WebAuthn Conditional UI & Cryptographic Origin-Binding Invariant

WebAuthn Conditional UI & Cryptographic Origin-Binding Invariant defines an industrial-grade behavioral standard for security engineering. Enforces strict WebAuthn Conditional Mediation (passkey autofill) on client interfaces alongside deterministic, immutable origin and Relying Party (RP) ID validation on the server. Guarantees that passkey assertions are cryptographically bound to the canonical, server-configured origin, neutralizing real-time reverse-proxy phishing (AitM) and spoofed domain vectors. 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: Dynamic Origin Validation, Missing Conditional UI Fallbacks, or Trusting Client-Reported Hostnames 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 DriftDynamic Origin Validation, Missing Conditional UI Fallbacks, or Trusting Client-Reported HostnamesCritical
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 client support for Conditional UI using window.PublicKeyCredential?.isConditionalMediationAvailable() before invoking navigator.credentials.get with mediation

Engineers MUST ALWAYS verify client support for Conditional UI using window.PublicKeyCredential?.isConditionalMediationAvailable() before invoking navigator.credentials.get with mediation: 'conditional'.

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 configure input fields with autocomplete='username webauthn' to bind standard autofill behaviors to the platform authenticator conditional mediation loop.

Engineers MUST configure input fields with autocomplete='username webauthn' to bind standard autofill behaviors to the platform authenticator conditional mediation loop.

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 manage an AbortController instance across client credential queries to cleanly terminate background mediation when users navigate away or manually submit credentials.

Engineers MUST manage an AbortController instance across client credential queries to cleanly terminate background mediation when users navigate away or manually submit credentials.

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

Services MUST NEVER derive expectedOrigin or expectedRPID from incoming request headers (such as Host, X-Forwarded-Host, Origin, or client-supplied payloads).

Services MUST NEVER derive expectedOrigin or expectedRPID from incoming request headers (such as Host, X-Forwarded-Host, Origin, or client-supplied payloads).

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 validate clientDataJSON.origin against an immutable, strictly configured allowlist of canonical origins using exact, case-sensitive matching.

Services MUST validate clientDataJSON.origin against an immutable, strictly configured allowlist of canonical origins using exact, case-sensitive matching.

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 verify clientDataJSON.type === 'webauthn.get' for authentication assertions and 'webauthn.create' for registration ceremonies.

Services MUST verify clientDataJSON.type === 'webauthn.get' for authentication assertions and 'webauthn.create' for registration ceremonies.

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 enforce cryptographic signature verification using the user's stored public key, and MUST verify that the SHA-256 hash of the expected RP ID strictly equals rpIdHash in authenticatorData.

Services MUST enforce cryptographic signature verification using the user's stored public key, and MUST verify that the SHA-256 hash of the expected RP ID strictly equals rpIdHash in authenticatorData.

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

Services MUST verify the single-use cryptographic challenge stored in the server session or cache, deleting it immediately upon verification to prevent replay attacks.

Services MUST verify the single-use cryptographic challenge stored in the server session or cache, deleting it immediately upon verification 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.
9

Services MUST assert that the user present (UP) bit is set in the authenticatorData flags, and check that the signature counter (signCount) strictly increments when non-zero.

Services MUST assert that the user present (UP) bit is set in the authenticatorData flags, and check that the signature counter (signCount) strictly increments when non-zero.

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 & Cryptographic Origin-Binding Invariant critical for AI coding assistants?

Enforces strict WebAuthn Conditional Mediation (passkey autofill) on client interfaces alongside deterministic, immutable origin and Relying Party (RP) ID validation on the server. Guarantees that passkey assertions are cryptographically bound to the canonical, server-configured origin, neutralizing real-time reverse-proxy phishing (AitM) and spoofed domain vectors. 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 (Dynamic Origin Validation, Missing Conditional UI Fallbacks, or Trusting Client-Reported Hostnames) 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