> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Passkeys & WebAuthn — Setup and Testing

> Enable and test Supabase passwordless passkeys (Phase 4) and WebAuthn second-factor MFA (Phase 3) for Encore OS.

**Last updated:** 2026-05-31 · Staleness marker: \[2026-05]

Encore OS supports **two distinct, complementary** WebAuthn features. They use
different Supabase APIs and different enablement switches — do not conflate them.

|               | Phase 3 — **WebAuthn second factor**                                      | Phase 4 — **Passwordless passkeys**                                            |
| ------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| What it does  | Adds a passkey as a *second factor* after password (raises AAL to `aal2`) | Lets the user sign in with **no password**                                     |
| SDK           | `supabase.auth.mfa.webauthn.register()` / `.authenticate()`               | `auth.registerPasskey()` / `auth.signInWithPasskey()` / `auth.passkey.*`       |
| Client opt-in | none                                                                      | `auth.experimental.passkey: true`                                              |
| Enablement    | Dashboard **only** (CLI/config push is rejected by the API)               | `config.toml` (CLI-pushable) **or** Dashboard/Management API                   |
| UI            | `MfaEnrollment` (Security settings) + `MfaChallenge` (login)              | `PasskeyManagementSection` (Security settings) + `PasskeySignInButton` (login) |
| Code          | `src/platform/auth/components/webauthn-mfa.ts`                            | `src/platform/auth/passkey/`                                                   |

> Passkeys are bound to the **Relying Party (RP) ID**. Changing the RP ID
> invalidates every existing passkey. Pick it carefully and keep it stable.

***

## 1. Phase 4 — Passwordless passkeys

### 1a. Server config

The canonical client (`src/integrations/supabase/client.ts`) is an
auto-generated, do-not-edit file, so the experimental opt-in lives in a
dedicated client at `src/platform/auth/passkey/passkey-client.ts` (distinct
storage key; sessions are bridged to the main client). **No app code change is
needed to enable passkeys** — only the Supabase project config below.

**Option A — `supabase/config.toml` (local dev & branch deploys).** This file
is write-protected in this repo; add the following by hand (or via the
Dashboard/Management API for hosted projects):

```toml theme={null}
[auth.passkey]
enabled = true

[auth.webauthn]
rp_display_name = "Encore OS"
# Local dev (Vite on :8080). For deployed projects set the bare production
# domain and HTTPS origins — see Option B.
rp_id = "localhost"
rp_origins = ["http://localhost:8080", "http://localhost:3000"]
```

`[auth.webauthn]` is **required** whenever `auth.passkey.enabled = true`.

**Option B — Dashboard / Management API (hosted/production).** In the
Dashboard: **Authentication → Passkeys → Enable Passkey authentication**, then
set:

* **Relying Party Display Name** — e.g. `Encore OS`
* **Relying Party ID** — bare domain, no scheme/port/path (e.g. `app.example.com`)
* **Relying Party Origins** — comma-separated HTTPS origins (≤ 5); each
  hostname must equal or be a subdomain of the RP ID
  (e.g. `https://app.example.com`). HTTPS required except for `localhost`,
  `127.0.0.1`, `[::1]`.

Equivalent Management API call:

```bash theme={null}
curl -X PATCH "https://api.supabase.com/v1/projects/$PROJECT_REF/config/auth" \
  -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "passkey_enabled": true,
    "webauthn_rp_display_name": "Encore OS",
    "webauthn_rp_id": "app.example.com",
    "webauthn_rp_origins": "https://app.example.com"
  }'
```

Requires `@supabase/supabase-js >= 2.105` (we run `^2.105.3`). Registering a
passkey requires a confirmed, non-anonymous, signed-in user; SSO and anonymous
users cannot register passkeys.

### 1b. Where users see it

* **Add / manage:** Settings → Security → **Passwordless sign-in** card.
* **Sign in:** the login screen shows **Sign in with a passkey** (only on
  WebAuthn-capable browsers).

***

## 2. Phase 3 — WebAuthn second factor (MFA)

The Supabase API **rejects** `[auth.mfa.web_authn] enroll_enabled = true` via
CLI/config push, so enable it in the **Dashboard → Authentication → MFA →
WebAuthn** (toggle enroll + verify). Until then, "Add a passkey" in the MFA
card fails at runtime with a sanitized error. No RP config is required beyond
the project's Site URL.

Users add it under Settings → Security → **Multi-Factor Authentication →
Passkeys**, and are prompted for it at the MFA challenge after entering their
password.

***

## 3. Testing

### Unit (no backend)

```bash theme={null}
npm run test -- tests/unit/platform/auth/passkey            # Phase 4
npm run test -- tests/unit/platform/auth/components/MfaEnrollment.test.tsx \
                tests/unit/platform/auth/components/MfaChallenge.test.tsx   # Phase 3
```

### End-to-end (real WebAuthn ceremony, virtual authenticator)

`tests/e2e/platform/pf-79-passkeys.spec.ts` drives the real ceremonies with a
CDP **virtual authenticator** (no physical device). The register → sign-in
loop needs passkeys enabled on the target project with an RP origin matching
the test origin, so it is **opt-in**:

```bash theme={null}
# UI-presence checks always run:
npm run test:e2e -- tests/e2e/platform/pf-79-passkeys.spec.ts

# Full register/sign-in loop (enable passkeys on the project first, per §1/§2):
PASSKEYS_E2E=1 npm run test:e2e -- tests/e2e/platform/pf-79-passkeys.spec.ts
```

Manual smoke test in Chrome: DevTools → **WebAuthn** tab → "Enable virtual
authenticator environment" → add a passkey in Security settings → sign out →
**Sign in with a passkey**.

***

## References

* Supabase passkeys guide: [https://supabase.com/docs/guides/auth/passkeys](https://supabase.com/docs/guides/auth/passkeys) (`#cli` for config)
* Supabase MFA guide: [https://supabase.com/docs/guides/auth/auth-mfa](https://supabase.com/docs/guides/auth/auth-mfa)
* `src/platform/auth/passkey/` (Phase 4), `src/platform/auth/components/webauthn-mfa.ts` (Phase 3)
