How the Lab is built
The IntegrAuth Lab is one Cloudflare Worker: a SvelteKit UI and a Hono API on the same edge runtime, backed by D1 (SQLite). All cryptography is hand-rolled on WebCrypto — no WebAuthn, CBOR or JWT libraries. This page documents the decisions and their trade-offs, the way we document the systems we build for clients.
Architecture
Requests hit a wrapper worker that applies security headers, then SvelteKit renders pages while /api/* is handed to Hono. State lives in D1: users, credentials
(public keys only), sessions (hashed tokens), single-use challenges, TOTP ciphertext, the
insert-only audit log, and rate-limit counters. Here is the passkey login flow end to end:
Key decisions & trade-offs
Passwordless only — no password column exists
Signup proves the mailbox with a one-time code; login is a passkey (WebAuthn). Trade-off: a passkey lives on a device, so recovery is an email-code re-verification that lets you enroll a new one. There is simply no password to phish, reuse or leak.
Everything hashed / encrypted at rest
Email codes and session tokens are stored as SHA-256; TOTP seeds as AES-GCM ciphertext under a Wrangler secret. A database read never yields a usable credential. Trade-off: we can never "show" a code — only verify one.
Insert-only audit log as the X-ray source
Every flow appends one sanitized event (a key-name denylist strips anything secret-ish). It is append-only — no update or delete path — so it doubles as a tamper-evident trail and as the data behind the learner X-ray. Trade-off: detail is deliberately coarse (counters, claim names, cookie names) — never values.
run_worker_first for one header chokepoint
A thin src/worker.ts wraps the SvelteKit worker and stamps the strict security-header set on every response — pages, JSON and static assets alike — so HSTS, nosniff, frame-ancestors none and the CSP can never be missed. SvelteKit still owns the per-page CSP that carries its hydration-script hashes.
Free-tier caps, on purpose
Workers/D1/Turnstile free tiers. Email shares one Resend account with another app, so the Lab is a light consumer: per-email, per-IP and a hard global daily cap, all enforced in D1. When the global cap is hit we return a friendly 429 rather than queue.
Hand-rolled WebAuthn / CBOR / JWT
The crypto that teaches is hand-written on WebCrypto — a minimal CBOR decoder, COSE→JWK import, ECDSA DER↔raw conversion, an ES256 JWS with an RFC 7638 kid. Trade-off: more code to test (and we do, with deterministic node:crypto fixtures) in exchange for zero crypto dependencies and a codebase that reads like the standards.
Honest limitations
- The demo JWT proves signing/verification and key discovery — it is NOT an authorization token. Real access control needs audience-scoped tokens and a resource server that enforces them (Phase 2: a full OIDC provider).
- Passkey attestation is deliberately not verified (attestation: "none", the industry default) — the Lab does not restrict which authenticators you may use.
- Workers free tier: no Durable Objects or Queues in v1. Sessions, challenges and rate-limit windows all live in D1, which is sufficient at this scale.
Explore
- Do the practicals →
- Public JWK set (/.well-known/jwks.json) →
- security.txt →
- The IntegrAuth Academy →
- Talk to IntegrAuth about your project →
The source will be published as a public GitHub repository at ship time.