Bot checks

Check that a person, not a script, is using your app's sign-up and sign-in forms, with your own Cloudflare Turnstile widget.

A bot check confirms that a person, not a script, is using your app's sign-up and sign-in forms, so nobody can create junk accounts in bulk or have your app mail sign-in links to strangers. Your forms show a Cloudflare Turnstile widget, and Authoric asks Cloudflare whether its token passed before the request runs.

You bring the widget. A Turnstile widget is bound to the domains it was made for, and your app's forms live on your domains, so the site key and secret key are yours.

What it checks

While the check is on, these five requests need a passed token. Every other auth route is unchanged.

RequestRoute
Sign upPOST /{appUid}/auth/signup
Password sign-inPOST /{appUid}/auth/login
Password reset emailPOST /{appUid}/auth/request-password-reset
Sign-in link emailPOST /{appUid}/auth/magic-link/request
Sign-in code emailPOST /{appUid}/auth/otp/request

The check is off until you turn it on.

Turn it on

  1. In the Cloudflare dashboard, open Turnstile and add a widget for your app's domains. Copy its site key and secret key.
  2. Add the widget to your app's own sign-up and sign-in forms, and send its token with the five requests above (next section). Do this first: once the check is on, a request without a token is refused, sign-in included.
  3. In the console, open your app, then App settings › Sign-in. Paste both keys, switch on Check for bots, and save.

The secret key is checked with Cloudflare when you save, and a key Cloudflare does not recognize is refused. It is then stored encrypted and is never shown again: the console says only that one is set. Type a new one to replace it. Turning the check off keeps both keys, so you can turn it back on without pasting them again.

Send the token

Your front end learns the site key from GET /{appUid}/auth/methods, so it can render the widget only when the check is on. botCheck is null while it is off.

{
  "password": true,
  "magicLink": true,
  "emailOtp": true,
  "botCheck": { "provider": "turnstile", "siteKey": "0x4AAAAAAA..." }
}

Render the widget with that site key, then send the token it produces in the X-Bot-Check header. A token is good for one request, so reset the widget after each attempt.

curl -X POST "$AUTHORIC_URL/$APP_UID/auth/login" \
  -H "Content-Type: application/json" \
  -H "X-Bot-Check: $TURNSTILE_TOKEN" \
  -d '{ "email": "dana@example.com", "password": "Str0ng!Passphrase" }'

The header is the same on all five requests. If your server calls these routes on a user's behalf, forward the token the user's browser produced: the check is on the request, wherever it comes from.

Errors

StatusCodeWhenWhat to do
403BOT_CHECK_FAILEDThe request has no X-Bot-Check header, or Cloudflare says its token did not pass.Show the widget again and retry with a fresh token.
503BOT_CHECK_UNAVAILABLECloudflare could not be reached to check the token. The request is refused, never let through.Retry in a moment.

Saving the keys in the console can answer:

StatusCodeWhen
400BOT_CHECK_SECRET_INVALIDCloudflare does not recognize the secret key. Copy it again from the widget's settings.
400BOT_CHECK_SETTINGS_INVALIDA key is not in Turnstile's format, or the check was switched on without both keys.
503BOT_CHECK_UNAVAILABLECloudflare could not be reached to confirm the secret key. Nothing was saved.

Testing locally

Cloudflare publishes test keys that work on any domain, localhost included. With the site key 1x00000000000000000000AA and the secret key 1x0000000000000000000000000000000AA, every check passes; the secret key 2x0000000000000000000000000000000AA makes every check fail. Switch to your real widget's keys before you go live.

On this page