ADR 0002: Clerk for user authentication
- Status: Accepted. Updated 2026-10-10: the production Clerk instance is live for prod (ADR 0017).
- Date: 2026-10-08
- Decider: Project owner
Context
Section titled “Context”Rhumbatron needs user sign-in for rhumbatron.com. Cloudflare Access protects internal operator
pages. It has no product sign-up flow for end users.
Decision
Section titled “Decision”Use Clerk (application Rhumbatron, app_3KOhGuEyKohDT9Lzrb2NvUVQZP4) with
@clerk/tanstack-react-start in apps/web.
src/start.tsrunsclerkMiddleware()after the server-function CSRF middleware.ClerkProviderwraps the body contents insrc/routes/__root.tsx./sign-inand/sign-uphost the Clerk components.
Secrets
Section titled “Secrets”- Local:
apps/web/.env.local, written byclerk init, ignored by Git. - Remote: Terraform makes
CLERK_SECRET_KEYa Workersecret_textbinding. ATF_VAR_environment variable supplies the value, so it never appears in Terraform source. VITE_CLERK_PUBLISHABLE_KEYis public. The build inlines it.
Consequences
Section titled “Consequences”- Authorization stays in Rhumbatron code (AGENTS.md section 40). Clerk only proves identity.
- The production Clerk instance is not configured yet. Configure it before the prod environment.
Diagram
Section titled “Diagram”One API call from a page. The browser never calls the API Worker directly. Server functions use
the API service binding.
sequenceDiagram participant B as Browser participant C as Clerk participant W as Web Worker (apps/web) participant A as API Worker (requireAuth) participant D as D1 B->>C: Sign in on /sign-in (Clerk components) C-->>B: Session B->>W: Request (clerkMiddleware after CSRF middleware) W->>W: Server function calls auth().getToken() W->>A: env.API.fetch with Authorization Bearer token A->>A: verifyToken(CLERK_SECRET_KEY), then azp check (CLERK_AUTHORIZED_PARTIES) A->>D: ensureIdentity (users, organizations, memberships) A->>D: canAccessProject (membership of the owning org) A-->>W: JSON response, or 401 on any failed check
Update (2026-10-10)
Section titled “Update (2026-10-10)”- The last Consequence is resolved. Prod uses the Clerk production instance on
rhumbatron.com.scripts/tf.tsloadsrhumbatron-clerk-secret-key-prodandrhumbatron-clerk-publishable-key-prodfrom the Keychain. Dev keeps the development instance. - Prod sign-in is email and password only. Google sign-in is disabled on the production instance.
envs/prodmanages the Clerk DNS records (var.clerk_dns_records, DNS only). Theclerk_production_instancecheck warns when the prod key is notpk_live_.- The build inlines the publishable key, and prod also sets it as the
plain_textbindingVITE_CLERK_PUBLISHABLE_KEYon the web Worker, so one bundle serves both environments (ADR 0017). - The API Worker verifies tokens with
@clerk/backendverifyTokenand checksazpagainstCLERK_AUTHORIZED_PARTIES. A token withoutazppasses only whenCLERK_ALLOW_MISSING_AZP = "true". Dev sets it for smoke tokens. Prod sets"false".