Production Checklist
Block unsafe effect-auth releases before production traffic.
Alpha software
effect-auth is dogfood alpha, not production-certified. Pin exact versions, inspect release and migration changes, and validate the complete deployment on your own risk model. Stop the release on any FAIL below.
Secrets and cryptography
- Generate high-entropy secrets outside source control and isolate environments.
- Derive scoped keys with
AuthSecretsFromRootLive, or independently rotate every enabled secret domain. - Verify every shared root/session/challenge/privacy key is 32-1024 UTF-8 or raw bytes and startup fails on
SecurityConfigurationError. - Use the built-in login-risk HMAC factory with a scoped 32-1024-byte key, or prove an expert custom factory provides equivalent domain separation; cache keys and device keys must be canonical digests and never raw IP/device material. Keep
locationKeybounded coarse context only. - Keep generated challenge/session IDs and session bearer secrets within the documented 16-128-byte bounds; do not weaken test configuration in production.
- Use
WebCryptoLive()on Workers; never assume a Node crypto implementation exists. - Select and benchmark password hashing for the runtime. Workers use a reviewed PBKDF2 policy; supported Node/Bun deployments should prefer the Argon2id adapters.
- For managed TOTP, protect
TotpSecretCipherkeys, retain old keys during rotation, and remove plaintext migration support only after all rows are encrypted. - For retained OAuth provider tokens, use the strict
OAuthProviderTokenCipherlayer with independent exact 32-byte keys, one current key, retained decrypt keys, complete-snapshot refresh CAS evidence, and concurrent refresh/revoke tests. Time-box and remove the explicit alpha legacy migration callback. - Document rotation, emergency revocation, and session impact.
FAIL: a default, missing, shared, logged, or plaintext configuration secret can reach production.
Browser boundary
| Verify | Release condition |
|---|---|
| Topology | Prefer same-origin /auth/*; test exact credentialed CORS if cross-origin |
| Cookies | HttpOnly, Secure, intended SameSite, path/domain, and valid __Host- constraints on real HTTPS |
| Origin policy | Exact production allowlist; hostile and missing origins rejected for every method except exact case-sensitive GET/HEAD/OPTIONS tokens |
| Proxy metadata | Forwarded IP/protocol trusted only behind a controlled proxy or service binding |
| WebAuthn | Production RP ID and origin tested in a real browser |
Choose and record one CSRF model: maintained origin-only, or origin plus app-owned double-submit. The exact fail-closed origin policy is mandatory in both models. If adding double-submit, verify token entropy, separate browser delivery, rotation/expiry, and authenticated-session binding. Origin checks do not configure CORS. FAIL: a hostile origin mutates state, cookies disappear, clients choose their policy IP, or the CSRF choice has no owned lifecycle.
| Sensitive cookie | Effective SameSite policy | Other invariant attributes |
|---|---|---|
| Session | Optional explicit lax, strict, or none; default lax | Secure, HttpOnly, __Host-, Path=/, no Domain |
| Trusted device | Optional explicit lax, strict, or none; default lax | Secure, HttpOnly, __Host-, Path=/, no Domain |
| Login approval | Optional explicit lax, strict, or none; default lax | Secure, HttpOnly, __Host-, Path=/, no Domain |
| Email process | Fixed lax | Secure, HttpOnly, __Host-, Path=/, no Domain |
| OAuth flow | Validated omitted/query gives Lax; exact form_post gives None | Secure, HttpOnly, __Host-, Path=/, no Domain |
AuthDomainConfigLive({ sessionCookie }) intentionally affects only the session cookie. It does not ambiently discover or configure trusted-device, login-approval, email-process, or OAuth-flow cookie policy.
Durable infrastructure
- Keep auth records in D1 or another durable database; keep shared limiter state in a Durable Object or equivalent, never isolate memory.
- Select the correct migration path: fresh generated baseline or upgrades for the existing ledger, never both.
- Apply reviewed migrations before dependent code and verify the production ledger afterward.
- Schedule encrypted, access-controlled backups; rehearse restore into an isolated environment.
- Exercise sign-in, registration, verification, reset, MFA, passkey, and recovery limits by every configured key.
- Test
429, shared cross-instance coordination, email delivery responsibility, provider failure, expiry, and replay.
FAIL: restore is unproven, a required store is absent, limits reset on isolate restart, or email reports success before durable delivery responsibility exists.
Sessions and optional features
| Feature | Required evidence |
|---|---|
| Sessions | Valid TTL ordering; issue, refresh, rotate, logout, expire, revoke, and revoke-other-sessions against durable rows |
| Trusted device | Integer TTL from 1 second through 400 days; commit immediately after durable trust while at least one whole second remains; malformed rows fail closed |
| Step-up | Recent required AAL for factor, recovery, billing, and other sensitive actions |
| Authorization | Every backend operation checks app policy independently of route and UI guards |
| OAuth | Exact redirect URIs, state/PKCE, validated query/form_post callback mode, linking policy, strict AAD-bound provider-token envelopes, retained-key rotation evidence; no wildcards |
| Device CLI | Canonical HTTPS endpoints, bounded exact scopes, explicit observer failure policy, timeout/response/attempt ceilings, OS-keychain credential custody |
| Passkeys | One-time challenges, counters, enrollment, revocation, production RP ID/origin |
| Recovery | Codes are one-time, hashed, rate-limited, and regenerate by replacing the prior set |
| Login risk | GeoIP TTL 1s-24h, precision 0-6, exact failure mode, cache capacity 1-10,000, malformed rows/provider output fail closed or follow explicit ignore policy |
| Providers | Exact HIBP/Turnstile/IPQS options, explicit cache/provider/observation failure modes, bounded responses and in-flight work, finite telemetry outcomes |
Disable unlaunched routes, bindings, credentials, and UI. FAIL: preview and production credentials, callbacks, RP IDs, or recovery material are interchangeable.
Configuration inventory remains incomplete, so there is intentionally no checked broad “all configuration hardened” gate. Login-risk enrichment construction, HMAC inputs, packaged provider/cache rows, process-local capacity, provider-token cipher/key custody, AuditLog, SecurityTimeline, packaged-provider telemetry, and OAuth device-client boundaries are bounded and maintained. Device CLI releases must keep observer/log/span data value-free and test an abort-ignoring transport, late unused responses, hanging/rejecting body cancellation, malformed/fatal-UTF-8 and RFC token/error fields, request-timeout retry, actual local expiry, slow_down, and attempt capacity against the selected runtime. Other residual areas are app-owned full public-suffix validation for passkey RP IDs, deployed browser E2E coverage, trusted proxy selection, and durable limiter capacity. Maintained OAuth/device HTTP operations have standard throttling, but the broader architecture gate remains open for those deployment concerns.
Before deploying the AuditLog strict cutover, verify D1 backup or Time Travel coverage and PostgreSQL transactional migration execution. Expect every legacy audit row to move to quarantine with requires-runtime-normalization; replay only reviewed rows through normalizeAuditEvent and AuditLogStore.insertMany.
Before deploying the SecurityTimeline strict cutover, apply SQLite/D1 0036 or PostgreSQL 0017 in staging and inspect quarantine. Expect all legacy timeline rows to leave the active read model; replay only reviewed rows through normalizeSecurityTimelineEvent and the timeline store insert path.
Observability and privacy
- Emit stable reason codes for auth failures, limiter decisions, provider errors, migration failures, and storage latency.
- Alert on baseline deviation and distinguish attack, dependency failure, and release regression.
- Redact cookies, credentials, codes, hashes, secrets, raw email/IP values, OAuth tokens, hostile headers, and complete auth rows from logs and traces.
- Define access and retention; keep health checks read-only.
FAIL: operators cannot diagnose a failed flow without exposing authentication material.
Cloudflare and Alchemy v2
- Pin the tested Alchemy, effect-auth, Effect, adapter, and Worker compatibility versions.
- Bind
AUTH_SECRETwithConfig.redacted, not a literal Worker environment value. - Use separate D1 databases, secrets, email bindings, Durable Objects, and Alchemy state per environment.
- Apply D1 migrations during deployment, not in request handling.
- Run local Alchemy/workerd tests and a deployed non-production D1 smoke against the public frontend URL.
- Protect
alchemy destroy, Cloudflare credentials, state files, deployment output, and logs.
The repository's Cloudflare checks use fake D1 for adapter tests and local Alchemy/workerd for the split-app smoke. They do not prove deployed D1 behavior. Pin a Worker compatibility date accepted by your Alchemy version; with nodejs_compat, keep it at or after Cloudflare's documented 2024-09-23 threshold and review compatibility-date changes before rollout.
Release gate
- Unit, contract, concurrency, HTTP, browser, hostile-origin, replay, expiry, and revocation tests pass.
- Canary owner, abort threshold, prior artifact/configuration, and rollback procedure are recorded.
- Old and new code can overlap with the deployed schema; prefer additive changes.
- Production-domain smoke covers sign-in, session read, refresh/logout, email, enabled continuation flows, and multiple
Set-Cookieheaders.
FAIL: telemetry is blind, smoke fails, migrations outrun compatible code, or rollback requires deleting production auth data.
Review Storage, Browser Client, Cloudflare Workers, and Alchemy v2 before approval.