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

46 lines
4.0 KiB
Markdown

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