Skip to main content

D18 — Cloudflare Turnstile on unauthenticated endpoints (RESOLVED)

Architecture decision record, relocated verbatim from the retired single-file docs/tenancy/ decision log. Status, thematic clusters, and how to record a new ADR: the decision log index.

App\Http\Middleware\VerifyTurnstile (alias turnstile) verifies a Turnstile token server-side against Cloudflare's siteverify API, applied to the two unauthenticated, abuse-prone endpoints: POST /api/v1/login (credential stuffing) and POST /api/v1/tenant-discovery (email enumeration). The SPA renders the widget (site_key) and submits cf-turnstile-response; failures return 422 on that field. config/turnstile.php (enabled, site_key, secret_key, verify_url) is env-driven. Non-prod uses Cloudflare's public test keys (always-pass: site 1x00000000000000000000AA / secret 1x0000000000000000000000000000000AA) with TURNSTILE_ENABLED=true, so dev exercises the real widget + siteverify flow with no Cloudflare account (verified end-to-end: no token → 422, dummy token → 200). The test suite forces it off (tests/bootstrap.php); TurnstileTest opts in with Http::fake(). Production sets real keys via 1Password → SSM.


← Decision log index