DEV Community

Cover image for Stop bot sign-ups on Supabase with Cloudflare Turnstile
Aravind Govindhasamy
Aravind Govindhasamy

Posted on Originally published at starter.budeglobal.in on

Stop bot sign-ups on Supabase with Cloudflare Turnstile

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

  1. The Turnstile widget runs in your page and produces a short-lived token.
  2. Your frontend passes that token to Supabase with the auth call.
  3. 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) and localhost for 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} />

Enter fullscreen mode Exit fullscreen mode

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 })

Enter fullscreen mode Exit fullscreen mode

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:

  1. Deploy the frontend with the widget and captchaToken in place.
  2. Check that sign-in still works (Supabase ignores the token until step 4).
  3. 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...

Enter fullscreen mode Exit fullscreen mode

Keep that variable set in every future build, or sign-in breaks once Supabase requires the CAPTCHA. See Sign-in methods.

Top comments (0)