Cloudflare Turnstile
Airspace uses Cloudflare Turnstile to protect public-facing forms from bots and automated abuse. Turnstile provides CAPTCHA-like protection with minimal user friction — most visitors will never see a challenge.
Protected Forms
Turnstile is active on the following pages:
- Login — prevents credential-stuffing attacks
- Registration — prevents mass account creation
- Onboarding (public tenant creation) — prevents automated tenant provisioning
Setup
1. Get Turnstile Keys
- Log in to your Cloudflare dashboard
- Navigate to Turnstile in the sidebar
- Click Add site
- Enter your site name and domain(s)
- Choose widget mode (Managed is recommended)
- Copy the Site Key and Secret Key
2. Configure Environment Variables
Add the following to your .env file:
TURNSTILE_SITE_KEY=your-site-key-here
TURNSTILE_SECRET_KEY=your-secret-key-here
Turnstile automatically activates when APP_ENV is staging or production. In local, testing, and any other environment it stays off by default, so developers don't need Cloudflare keys to work on login/registration/onboarding forms. Set TURNSTILE_ENABLED=true only if you want to force it on in a non-production environment (e.g., to verify the widget behavior locally).
3. Deploy
No additional steps are needed. The Turnstile widget will automatically appear on the protected forms.
Disabling Turnstile
To disable Turnstile (e.g., for development), set:
TURNSTILE_ENABLED=false
When disabled:
- The widget does not render on any form
- Server-side validation is skipped (always passes)
- No requests are made to Cloudflare
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Widget not appearing | TURNSTILE_ENABLED is false or missing site key | Check .env values |
| "CAPTCHA verification failed" error | Token expired or invalid secret key | Verify TURNSTILE_SECRET_KEY matches your Cloudflare dashboard |
| Forms work but no widget | JavaScript blocked | Ensure challenges.cloudflare.com is not blocked by CSP or ad blockers |
| Verification fails after long page idle | Turnstile tokens expire after ~5 minutes | The widget auto-refreshes; if issues persist, reload the page |