Bots find sign-up forms within days of launch. They create junk accounts and, worse, use your password-reset and magic-link forms to send email to people who never asked for it, which hurts your sending reputation. Supabase Auth can require a CAPTCHA on every sign-up, sign-in and reset, and Cloudflare Turnstile is free and usually invisible to real users.
How it works
- The Turnstile widget runs in your page and produces a short-lived token.
- Your frontend passes that token to Supabase with the auth call.
- Supabase checks the token with Cloudflare using your secret key, and refuses the request if it's missing or invalid.
Your backend doesn't need any changes.
1. Create a Turnstile widget
In the Cloudflare dashboard, open Turnstile → Add widget :
- Name: your app's name
-
Hostnames: your domain (
example.com) andlocalhostfor development - Widget mode: Managed is a good default; most visitors never see a challenge
Copy the Site key (public, goes in your frontend) and the Secret key (goes in Supabase).
2. Add the widget to your forms
With React, the @marsidev/react-turnstile package is the simplest route:
npm install @marsidev/react-turnstile
import { Turnstile } from "@marsidev/react-turnstile"
const [captchaToken, setCaptchaToken] = useState<string>()
<Turnstile siteKey={TURNSTILE_SITE_KEY} onSuccess={setCaptchaToken} />
Then pass the token to every auth call Supabase protects:
await supabase.auth.signUp({ email, password, options: { captchaToken } })
await supabase.auth.signInWithPassword({ email, password, options: { captchaToken } })
await supabase.auth.signInWithOtp({ email, options: { captchaToken } })
await supabase.auth.resetPasswordForEmail(email, { captchaToken })
A token works once. After a failed attempt, reset the widget to get a fresh one.
3. Deploy the frontend first
This order matters. Once Supabase requires a CAPTCHA, every sign-in without a token is refused, including from users still running your previous build. So:
- Deploy the frontend with the widget and
captchaTokenin place. - Check that sign-in still works (Supabase ignores the token until step 4).
- Then turn on enforcement.
4. Turn on CAPTCHA protection in Supabase
In the Supabase dashboard, open Authentication → Attack Protection (some dashboard versions call it Bot and Abuse Protection ). Turn on Enable CAPTCHA protection , choose Cloudflare Turnstile , paste the Secret key and save.
5. Test
- Sign up in a normal browser: it should work, usually without a visible challenge.
- Call the auth API without a token (for example with
curl): Supabase should refuse it with a CAPTCHA error.
Troubleshooting
| Symptom | Cause |
|---|---|
| "captcha verification process failed" for everyone | The secret in Supabase doesn't match the site key's widget, or the hostname isn't on the widget's list. |
| Works in production, fails on localhost | Add localhost to the widget's hostnames. |
| Fails on the second try | Tokens are single-use; reset the widget after each attempt. |
Using it with FastAPI Supabase Starter
The widget and the captchaToken wiring are already in the sign-in page. Put the site key in .env, rebuild and deploy the frontend, then add the secret key in Supabase:
TURNSTILE_SITE_KEY=0x4AAAAAAA...
Keep that variable set in every future build, or sign-in breaks once Supabase requires the CAPTCHA. See Sign-in methods.
Top comments (0)