Background
Vocabulary, crypto primitives, and which package owns each job.
Background
Read this once before wiring Better Auth Zero Access by Railman. It is the glossary and package map for the rest of the docs.
What problem this solves
Users want encrypted data that survives a full database leak. That means:
- The server can store ciphertext and metadata.
- The server never holds the key that opens that ciphertext.
- Step-up for account actions stays separate from unlocking sealed data.
zero-access is a set of Better Auth plugins and small client libraries that implement those rules.
Words you will see
| Term | Meaning |
|---|---|
| MEK | Master encryption key. Lives in the browser after unlock. Never sent to the server. |
| Wrap | MEK encrypted under a password, passkey PRF secret, or recovery phrase. Server stores wraps only. |
| PRF | WebAuthn extension that derives a site-bound secret from the authenticator. Used as a daily unlock factor. |
| KEK | Key-encryption key derived client-side (from PRF, password, or BIP-39 seed) to produce a wrap. |
| Elevate / sudo | Short-lived step-up claim on the session. Privileged product routes check it. Does not unwrap the MEK. |
| L0 / L1 / L2 | Session · elevate · vault unlock. Three independent proofs. |
| Blind store | Server API that accepts and returns wrap ciphertext without ever seeing MEK material. |
| amr | Authentication method references on an elevate claim (passkey, totp, password). |
Trust boundary in one table
| Lives in the browser | May cross the wire | Lives on the server |
|---|---|---|
| MEK (after unlock) | Wrap ciphertext + KDF params | Wrap rows, MEK metadata |
| PRF output | Signed elevate claim (session) | Session + HMAC-sealed claims |
| Vault password | Account-recovery public key | Rate limits, associations |
| Recovery mnemonic | Sealed E2E envelopes (chat) | Ciphertext only |
If a value is in the left column, product code must never put it in a request body. The plugins enforce a denylist (M6) for the obvious failures.
Crypto (what we use, not a tutorial)
| Primitive | Where | Role |
|---|---|---|
| WebAuthn + PRF | Browser | Login and/or daily vault unlock secret |
| PBKDF2-SHA256 | Browser | Password and recovery wraps (high iteration count) |
| HKDF-SHA256 | Browser / server | Domain-separated keys (PRF info, elevate MAC, recovery) |
| AES-GCM (via helpers) | Browser | Seal app payloads with MEK |
| BIP-39 | Browser | Recovery phrase → seed → recovery wrap (and optional account-recovery signing key) |
| Ed25519 | Browser + server verify | Account recovery challenge-sign (Mode B) |
| HMAC | Server | Elevate claims and login-factor stamps bound to BETTER_AUTH_SECRET |
You do not need to call most of these directly. zero-vault and the passkey client helpers compose them.
For introductory extended reading, see WebAuthn and PRF, Crypto primitives, and Crypto and package rationale.
Packages
Install only what the product needs.
| Package | Plugin / API | You need it when… |
|---|---|---|
better-auth | core | Always |
@better-auth/passkey | passkey factory | Passed into enhancePasskey |
@railman/auth-zero-access-passkey | enhancePasskey(passkey, …) | Passkey login or PRF vault unlock |
@railman/auth-login-factor | loginFactors() | Signed L0 stamps, requireAccess maps |
@railman/auth-elevate | elevate() | Step-up / sudo (passkey optional) |
@railman/auth-zero-access | zeroAccess() | Passkey-centered blind MEK store, recovery, optional E2E relay |
@railman/zero-vault | createVaultClient() | Product create / daily unlock / reset UX |
@railman/zero-e2e | X3DH + Double Ratchet | 1:1 sealed messaging on a Vault-derived identity |
Server install order when you use everything:
...enhancePasskey(...).plugins → loginFactors → elevate → zeroAccess()PRF salt stays in the browser crypto config (defineZeroAccessPasskeyCryptoConfig), not in the server passkey plugin.
How adoption branches
See the full matrix in Use cases (A–E). Short version:
| Entry path | Composition | Where it leads |
|---|---|---|
| Elevate independently | elevate (+ login-factor; passkey optional) | Password/TOTP/passkey step-up at /elevate |
| Passkey-centered stack | enhancePasskey + guarded zeroAccess | Vault at /vault, then Chat at /chat |
Vault is the first product layer on the passkey/PRF foundation. Chat extends Vault: unlock supplies the identity seed that zero-e2e consumes. The crypto library does not run WebAuthn itself, but the Chat product path is not standalone.
Hard rules
- Elevate success must not call vault unlock.
- Vault unlock must not grant admin routes.
- Authorize from the server session (signed claim), never from client-supplied JSON.
- Recovery phrase is for reset, not everyday unlock.
- Chat identity must come from the unlocked Vault path; demo password bindings are not the product architecture.
Maturity
Packages are still early (0.0.x). Crypto and first Better Auth endpoints
exist; treat production use as your own security review.
Next
- How it works — L0 · L1 · L2
- Elevate and sudo — with and without passkeys
- Quick start — compose and ship