auth-yes/tasks/complete/2026-0824.01.jul.feat.webauthn.prf-extension-1200.md
google-labs-jules[bot] e1555f14fc feat: implement WebAuthn PRF extension for client-side HKDF key derivation
* Added `prf_enabled` and `prf_salt` columns to the `passkeys` table.
* Updated registration API endpoints to request and store the PRF extension capability and generate a secure salt.
* Updated the login API endpoints to map stored PRF salts into the `evalByCredential` array for the WebAuthn challenge.
* Enhanced the client-side WebAuthn SDK (`auth-client.js`) to extract the PRF Base64URL string output, decode it into a `Uint8Array`, and securely derive a 256-bit AES-GCM Key Encryption Key (KEK) via `crypto.subtle.deriveKey` using the `auth-yes:prf:device-share:v1` info string.
* Implemented graceful fallbacks throughout the stack to ensure registration and standard logins proceed if PRF is unsupported.
* Added corresponding unit tests to verify PRF flow and rejection logic.
* Verified visual and functional changes for the optional username input on the login page via Playwright scripts.

Co-authored-by: mrteye <1945243+mrteye@users.noreply.github.com>
2026-08-24 07:46:22 +00:00

4.0 KiB

TASK METADATA

  • Target Files: server/main.ts, server/db.ts, ui/public/auth-client.js, ui/components/RegisterPage.tsx, ui/components/LoginPage.tsx
  • Core Objective: Implement WebAuthn PRF extension support for progressive feature detection and Key Encryption Key (KEK) derivation during registration and login, with graceful fallback.
  • Dependencies: WebCrypto API natively in Deno/browser, SimpleWebAuthn v13 for passing PRF extension options.
  • Additional Important Notes: This task establishes the PRF derivation pipeline. SSS multi-share reconstruction will integrate in a future story (3.3). If PRF is unsupported, registration/login must proceed normally without breaking standard WebAuthn flows.

Architectural Considerations & Risks

  • Risks:
    • Authenticator Compatibility: Not all authenticators support the WebAuthn PRF extension. A hard failure when PRF is missing would lock users out. The progressive fallback design is critical to prevent regressions in standard authentication.
    • Extension Types & SDK Mapping: Passing the exact extension payloads for PRF (eval.first, eval.second) in generateRegistrationOptions and generateAuthenticationOptions might require careful type mapping if SimpleWebAuthn types are strict.
    • Database Migrations: Modifying the passkeys table to include prf_enabled and prf_salt must maintain compatibility with existing passkey rows (which will default to false/null).
  • Alternatives:
    • Traditional server-side wrapping (HSM/KMS) or user passwords could be used for key derivation. However, the WebAuthn PRF extension natively binds the encryption key material to the hardware authenticator itself, preserving Auth-Yes's passwordless UX and zero-trust properties without transmitting raw secrets.

Proposed Implementation

1. Database Schema Updates (server/db.ts)

  • Modify the passkeys table schema to include a prf_enabled BOOLEAN DEFAULT FALSE column.
  • Add a prf_salt column (binary or hex string) to store the 32-byte cryptographic salt generated during registration.

2. Registration Flow (Server & Client)

  • Server (server/main.ts): In the /api/register/challenge endpoint, ensure the prf: {} extension is requested via generateRegistrationOptions.
  • Client (ui/public/auth-client.js): Execute navigator.credentials.create() through the client SDK. Extract getClientExtensionResults()?.prf.
  • Server (server/main.ts): In the /api/register/verify endpoint, inspect the extension results to check if PRF is enabled (prf.enabled === true). If supported, generate a 32-byte secure random salt (prf_salt). Store prf_enabled: true and the prf_salt alongside the new passkey record.

3. Login Flow (Server & Client)

  • Server (server/main.ts): In the /api/login/challenge endpoint, retrieve the user's prf_salt if their passkey has prf_enabled. Include the prf: { eval: { first: <prf_salt> } } extension payload in generateAuthenticationOptions.
  • Client (ui/public/auth-client.js):
    • Execute navigator.credentials.get() with the provided PRF evaluation salt.
    • Check getClientExtensionResults()?.prf?.results?.first for the PRF output.
  • Client-Side KEK Derivation:
    • If PRF output exists, use it as Input Keying Material (IKM) for WebCrypto HKDF to derive a 256-bit AES-GCM Key Encryption Key (KEK).
    • HKDF Parameters:
      • Hash: SHA-256 (RFC 5869)
      • Salt: 32-byte cryptographic salt (stored with passkey record)
      • Info: new TextEncoder().encode("auth-yes:prf:device-share:v1")
  • Progressive Fallback:
    • If getClientExtensionResults()?.prf is missing or fails, gracefully bypass the KEK derivation step and continue standard signature-only WebAuthn login.

4. UI Integration (ui/components/RegisterPage.tsx, ui/components/LoginPage.tsx)

  • (Optional but recommended) Include minor, non-blocking UI indicators or debug logs to signify when advanced hardware encryption (PRF) is successfully negotiated, aiding in development and progressive feature adoption.