Supabase Auth error
captcha protection: request disallowed (no captcha_token found)
"captcha protection: request disallowed (no captcha_token found)" is the 400 message Supabase Auth returns, with the error code captcha_failed, when CAPTCHA protection is on for the project and the request body has no gotrue_meta_security.captcha_token. supabase-js sends that field only when you pass options.captchaToken. Auth runs the check on sign up, sign in with a password, OTP and magic link, password recovery and resend, so the sign-in and reset forms need a token too, not only sign up. Pass the token from the hCaptcha or Turnstile widget, and get a fresh one for each call. If the message ends in a provider code such as (timeout-or-duplicate), a token was sent and the provider rejected it.
Which Supabase Auth calls need the captchaToken?
In the supabase/auth router the CAPTCHA check runs on /signup, /token, /recover, /resend, /magiclink and /otp, and on the SSO and passkey authentication options routes. On /token it skips the refresh_token, pkce and id_token grants, so refreshing a session needs no token but signInWithPassword does. The check is also skipped for a request with admin credentials, which is why a test with the service role key can pass while the browser fails. The Supabase guide says CAPTCHA can protect sign-in, sign-up and password reset forms, and shows signUp.
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 });What does the request look like with and without a token?
On 2026-10-31 we ran @supabase/supabase-js 2.118.0 against a local stub server and printed the request bodies. Without options.captchaToken, signUp sent an empty gotrue_meta_security object. With it, the token sat inside that object as captcha_token. signInWithPassword, signInWithOtp and resetPasswordForEmail sent the same field. This shows what the client sends; it did not run the Auth server, so the message text above comes from the supabase/auth source.
POST /auth/v1/signup {"email":"a@example.com","password":"pw123456","data":{},"gotrue_meta_security":{}, ...}
POST /auth/v1/signup {"email":"a@example.com","password":"pw123456","data":{},"gotrue_meta_security":{"captcha_token":"TOKEN123"}, ...}What do the other CAPTCHA errors from Supabase Auth mean?
When a token is present, Auth sends it with the secret you saved in the dashboard to hCaptcha or Turnstile. If the provider answers success false, the message is "captcha protection: request disallowed (" followed by the provider error codes joined by commas, with HTTP 400. With Turnstile, "(timeout-or-duplicate)" means the token was already validated and "(invalid-input-secret)" means the secret saved in Supabase is wrong; see the siteverify codes. If Auth cannot reach the provider or read its answer, it returns a 500 with "captcha verification process failed". Supabase lists captcha_failed as: CAPTCHA challenge could not be verified with the CAPTCHA provider.
Checklist
How to fix it, then check the fix
- 01Open the Supabase Dashboard, Auth protection settings, and find the CAPTCHA toggle under Bot and Abuse Protection. The provider secret key goes there, the site key goes in your frontend widget.
- 02Pass the token from the widget callback to every Auth call the form makes:
const { error } = await supabase.auth.signInWithPassword({ email, password, options: { captchaToken }, }); - 03Store the token in state from the widget callback (onSuccess for Turnstile, onVerify for hCaptcha) and keep the submit button disabled while captchaToken is empty.
- 04Get a new token for each call. Reset the widget after the Auth call; the Supabase guide calls resetCaptcha() on the hCaptcha ref after the auth call.
- 05Put a widget on every form that calls Auth, including login and password reset, or decide to turn CAPTCHA off in the dashboard. A project with CAPTCHA on rejects those calls without a token too.
- 06Submit the form on the deployed site and open the network tab: the /auth/v1/signup or /auth/v1/token response is no longer a 400 with captcha_failed.
Related
Related guides
Sources
Official docs behind this checklist
- Supabase, Enable CAPTCHA protection
- Supabase, Error codes (captcha_failed)
- supabase/auth, internal/api/middleware.go (verifyCaptcha)
- supabase/auth, internal/api/api.go (routes)
- supabase/auth, internal/security/captcha.go
- supabase-js, auth-js types.ts (captchaToken options)
- Cloudflare Turnstile, Server-side validation
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
Does refreshing a session need a captcha token?
No. On /token, supabase/auth skips the check for the refresh_token, pkce and id_token grants. Password sign-in uses the password grant, which is checked.
Why does it work with curl and my service role key?
The supabase/auth middleware skips the CAPTCHA check when the request carries admin credentials. Test with the anon key, as the browser does, to see the real behavior. Never put the service role key in browser code.
Should I also verify the Turnstile token in my own route?
Not with the same token. With Supabase CAPTCHA on, Auth validates it with the provider, and a Turnstile token can only be validated once, so a second check returns timeout-or-duplicate. Use one verifier.