--- name: passkeys-webauthn-identity-expert description: "Expert guide for FIDO2 / WebAuthn Level 3 Passkeys, SimpleWebAuthn, Conditional UI autofill, step-up cryptographic authentication for FinTech, and DPoP token binding / Panduan ahli autentikasi tanpa kata sandi Passkeys (WebAuthn L3 / FIDO2) dan DPoP." author: "Roedy Rustam" version: "4.4.2" --- # Passkeys, WebAuthn Level 3 & Cryptographic Identity Expert (2026 Edition) [English](#english) | [Bahasa Indonesia](#bahasa-indonesia) --- ## English ### Orchestration & Integration - **Identity & Access Control**: `authentication-identity-expert`, `fga-rebac-authorization-expert`, `zero-trust-secret-vault` - **FinTech & High-Value Step-Up Auth**: `doku-payment-gateway`, `saas-billing`, `double-entry-ledger-fintech-expert` - **Frontend & Apple/Mobile Ecosystems**: `senior-frontend`, `apple-ecosystem-expert`, `mobile-expo-expert`, `anti-slop` - **Lifecycle & Governance**: `brainstorming`, `zero-to-prod-orchestrator`, `session-memory-manager` ### Description Production security guide for phishing-resistant **FIDO2 / WebAuthn Level 3 Passkeys** (`@simplewebauthn/server` & `@simplewebauthn/browser`), **Conditional UI** browser autofill (`autocomplete="username webauthn"`), **Related Origin Requests (ROR)** for multi-domain SaaS apps, cryptographic **Step-Up Authentication** for high-value financial payouts, and **RFC 9449 DPoP (Demonstrating Proof-of-Possession)** sender-constrained tokens. ### Trigger Conditions - Implementing passwordless sign-up/sign-in with FaceID, TouchID, Windows Hello, or hardware YubiKeys. - Requiring biometric cryptographic confirmation before executing high-risk actions (API key reveal, wire transfer, role elevation). - Sharing Passkeys across web (`app.example.com`), regional domains, and native iOS (`apple-app-site-association`) / Android (`assetlinks.json`) apps. - Preventing replay attacks on stolen OAuth/session tokens using DPoP. --- ### Core Security Invariants | Invariant | Vulnerability Prevented | Implementation Requirement | | :--- | :--- | :--- | | **Single-Use Challenge** | Replay Attacks | Generate 32-byte CSPRNG challenge, store in Redis with 5-minute TTL, and delete atomically on verification. | | **Strict RP ID & Origin Verification** | Phishing / MITM | Verify `expectedOrigin` and `expectedRPID` strictly against allowlisted production origins. | | **User Verification (`uv`)** | Unattended Device Abuse | Enforce `userVerification: 'required'` for financial step-up and admin authentication. | | **Sign Count Anomaly Detection** | Cloned Hardware Authenticators | Compare `authenticationInfo.newCounter` when `> 0` against stored `counter` to detect cloned keys. | --- ### Core Implementation Patterns #### 1. Passkey Registration & Verification Server (`@simplewebauthn/server`) ```typescript // src/auth/passkey-service.ts import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, type VerifiedRegistrationResponse, } from '@simplewebauthn/server'; import { Redis } from '@upstash/redis'; const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!, }); const RP_NAME = 'Vibes Sovereign Platform'; const RP_ID = process.env.WEBAUTHN_RP_ID ?? 'localhost'; const EXPECTED_ORIGIN = process.env.WEBAUTHN_ORIGIN ?? 'http://localhost:3000'; export async function beginPasskeyRegistration(user: { id: string; email: string; existingCredentialIdsB64Url: string[]; }) { const options = await generateRegistrationOptions({ rpName: RP_NAME, rpID: RP_ID, userName: user.email, userID: new TextEncoder().encode(user.id), attestationType: 'none', excludeCredentials: user.existingCredentialIdsB64Url.map((id) => ({ id })), authenticatorSelection: { residentKey: 'preferred', userVerification: 'required', }, }); // Persist challenge with strict 300-second expiration await redis.set(`webauthn:reg:${user.id}`, options.challenge, { ex: 300 }); return options; } export async function completePasskeyRegistration( userId: string, responseBody: unknown ): Promise { const challengeKey = `webauthn:reg:${userId}`; const expectedChallenge = await redis.get(challengeKey); if (!expectedChallenge) { throw new Error('Passkey registration challenge expired or already consumed'); } // Delete challenge immediately (single-use invariant) await redis.del(challengeKey); const verification = await verifyRegistrationResponse({ response: responseBody as Parameters[0]['response'], expectedChallenge, expectedOrigin: EXPECTED_ORIGIN, expectedRPID: RP_ID, requireUserVerification: true, }); if (!verification.verified || !verification.registrationInfo) { throw new Error('Passkey cryptographic verification failed'); } return verification; } ``` --- #### 2. WebAuthn Level 3 Client Capability Detection & Conditional UI ```typescript // src/client/passkey-capabilities.ts export interface ClientPasskeySupport { conditionalMediation: boolean; platformAuthenticator: boolean; } export async function detectPasskeyCapabilities(): Promise { if (typeof window === 'undefined' || !window.PublicKeyCredential) { return { conditionalMediation: false, platformAuthenticator: false }; } const [conditionalMediation, platformAuthenticator] = await Promise.all([ PublicKeyCredential.isConditionalMediationAvailable?.() ?? Promise.resolve(false), PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable?.() ?? Promise.resolve(false), ]); return { conditionalMediation, platformAuthenticator }; } ``` --- #### 3. Cryptographic Step-Up Authentication & RFC 9449 DPoP Proof Validator (TypeScript) Protect financial transfers and admin actions with biometric step-up and sender-constrained DPoP proofs: ```typescript // src/auth/dpop-validator.ts import { createHash } from 'node:crypto'; export interface DPoPProofHeader { typ: 'dpop+jwt'; alg: 'ES256' | 'RS256'; jwk: { kty: string; crv?: string; x?: string; y?: string; n?: string; e?: string }; } export interface DPoPClaims { jti: string; htm: string; htu: string; iat: number; ath?: string; } export function computeAccessTokenHash(accessToken: string): string { return createHash('sha256').update(accessToken, 'ascii').digest('base64url'); } export function validateDPoPProof( proofJwt: string, expectedMethod: string, expectedUri: string, accessToken?: string ): { valid: boolean; jti: string; clientPublicKeyJwk: unknown } { const parts = proofJwt.split('.'); if (parts.length !== 3) { throw new Error('[DPoP] Invalid JWT format'); } const header = JSON.parse(Buffer.from(parts[0], 'base64url').toString('utf8')) as DPoPProofHeader; const claims = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')) as DPoPClaims; if (header.typ !== 'dpop+jwt') { throw new Error(`[DPoP] Header typ must be "dpop+jwt", got: ${header.typ}`); } // Validate HTTP method and URL parity if (claims.htm.toUpperCase() !== expectedMethod.toUpperCase()) { throw new Error(`[DPoP] Method mismatch: expected ${expectedMethod}, got ${claims.htm}`); } const cleanExpectedUri = expectedUri.split('?')[0]; const cleanProofUri = claims.htu.split('?')[0]; if (cleanExpectedUri !== cleanProofUri) { throw new Error(`[DPoP] URI mismatch: expected ${cleanExpectedUri}, got ${cleanProofUri}`); } // Clock skew: proof must be within +/- 60 seconds const now = Math.floor(Date.now() / 1000); if (Math.abs(now - claims.iat) > 60) { throw new Error('[DPoP] Proof timestamp expired or out of allowed window'); } // Access token binding check if (accessToken) { const expectedAth = computeAccessTokenHash(accessToken); if (claims.ath !== expectedAth) { throw new Error('[DPoP] Access token hash mismatch'); } } return { valid: true, jti: claims.jti, clientPublicKeyJwk: header.jwk, }; } ``` --- ### Verification Checklist - [ ] WebAuthn challenges are ≥32 random bytes, bound to the user/session in Redis with a ≤300s TTL, and deleted immediately upon first verification attempt. - [ ] Login input fields include `autoComplete="username webauthn"` to enable seamless Conditional UI Passkey autofill. - [ ] Multi-domain deployments publish `/.well-known/webauthn` (Related Origin Requests) and mobile `apple-app-site-association` / `assetlinks.json`. - [ ] Users can register multiple passkeys and have a verified account recovery flow. ### Sovereign Execution, Token Efficiency & Invariants | Dimension | Sovereign Mandate & Deterministic Contract | | :--- | :--- | | **Frontier Model Cascade** | High Fast / High (`Grok 4.7 High Fast`, `Claude Sonnet 5.5 High`, `Gemini 4 Pro`) | | **Availability Fallback** | `Gemini 4 Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6`; `Grok 4.7` -> `Grok 4.6 High Fast` (Zero unhandled missing-model errors) | | **KV-Cache & Token Protocol** | Pin static instructions & schemas at prompt prefix (`kv-cache-prefix-optimizer`); pass surgical `CONTEXT_MAP.md` slices (<250 tokens/call) during Swarm Fan-Out | | **SaaT CLI & MCP Dispatch** | CLI: `vibes skill exec passkeys-webauthn-identity-expert ` / MCP: `vibes_skill_dispatch`, `vibes_critic_validate`, `vibes_self_heal`, `vibes_eval_sandbox`, `vibes_verify_invariants` | | **NFR & Anti-Slop Invariants** | Zero OWASP Top 10 vectors, 0 hardcoded secrets, >= 90% branch coverage on domain logic, 0 Anti-Slop violations; 100% complete zero-placeholder output verified via `node scripts/check-anti-slop.js --strict` | --- --- ## Bahasa Indonesia ### Integrasi Orkestrasi - **Identitas & Kontrol Akses**: `authentication-identity-expert`, `fga-rebac-authorization-expert`, `zero-trust-secret-vault` - **FinTech & Step-Up Auth**: `doku-payment-gateway`, `saas-billing`, `double-entry-ledger-fintech-expert` - **Ekosistem Frontend & Mobile**: `senior-frontend`, `apple-ecosystem-expert`, `mobile-expo-expert`, `anti-slop` - **Siklus Hidup & Tata Kelola**: `brainstorming`, `zero-to-prod-orchestrator`, `session-memory-manager` ### Deskripsi Panduan keamanan produksi untuk autentikasi tanpa kata sandi tahan-phishing menggunakan **FIDO2 / WebAuthn Level 3 Passkeys** (`@simplewebauthn/server`), **Conditional UI autofill**, **Step-Up Authentication** biometrik untuk transaksi bernilai tinggi, dan pengikatan token **DPoP (RFC 9449)**. ### Panduan Arsitektur Inti 1. **Challenge Sekali Pakai**: Simpan `challenge` di Redis dengan TTL maksimal 5 menit dan segera hapus (`DEL`) saat permintaan verifikasi diterima untuk mencegah *replay attack*. 2. **Verifikasi Origin & RP ID Ketat**: Selalu validasi `expectedOrigin` dan `expectedRPID` di sisi server. 3. **Step-Up Biometrik**: Wajibkan `userVerification: 'required'` sebelum mengeksekusi penarikan dana atau perubahan hak akses admin. ### Daftar Periksa Verifikasi (Checklist) - [ ] Challenge disimpan di Redis dengan TTL $\le 300$ detik dan dihapus pada percobaan verifikasi pertama. - [ ] Input login menyertakan atribut `autoComplete="username webauthn"`. - [ ] File `/.well-known/webauthn` dan `apple-app-site-association` dikonfigurasi untuk dukungan lintas domain/aplikasi. ### Matriks Eksekusi Berdaulat, Efisiensi Token & Invarian | Dimensi | Mandat Berdaulat & Kontrak Deterministik | | :--- | :--- | | **Kaskade Model Frontier** | High Fast / High (`Grok 4.7 High Fast`, `Claude Sonnet 5.5 High`, `Gemini 4 Pro`) | | **Invarian Fallback Ketersediaan** | `Gemini 4 Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6`; `Grok 4.7` -> `Grok 4.6 High Fast` (Bebas gagal saat model belum tersedia/kuota penuh) | | **Protokol Hemat Token & KV-Cache** | Kunci instruksi & skema statis di prefix awal (`kv-cache-prefix-optimizer`); kirim potongan `CONTEXT_MAP.md` (<250 token/panggilan) saat Fan-Out | | **Eksekusi SaaT CLI & MCP** | CLI: `vibes skill exec passkeys-webauthn-identity-expert ` / MCP: `vibes_skill_dispatch`, `vibes_critic_validate`, `vibes_self_heal`, `vibes_eval_sandbox`, `vibes_verify_invariants` | | **Invarian NFR & Anti-Slop** | Nol celah OWASP Top 10, 0 kredensial hardcoded, cakupan pengujian >= 90% pada logika domain, 0 pelanggaran Anti-Slop; kode 100% tuntas tanpa placeholder diverifikasi melalui `node scripts/check-anti-slop.js --strict` |