Platform

Cloudflare Turnstile

Bot protection on the hosted pages, validated on the server.

Where it applies#

PageChecked submissions
Hosted AuthKit pages on 3een subdomainsPassword sign-in, password sign-up, forgot password, sending a magic code
3een’s own sign-in pagesPassword sign-in, sign-up, forgot password, resending a verification code

Follow-up steps are not checked again: an MFA code, a reset with its emailed link, a verification code or the return from a social provider already carry their own proof. The widget runs invisibly and asks for a click only when Cloudflare is unsure.

Server-side validation#

  • The token is verified with Cloudflare's Siteverify API on the server before anything reaches the authentication API.
  • The token is removed from the request before it is forwarded. The API never sees it.
  • The visitor's IP is read from the right-hand end of X-Forwarded-For, the entry the trusted proxy added, never from a value the client supplied.
  • The secret key stays on the server; the page only ever has the public site key.

Failures#

A failed or missing check is 403 with { code: "bot_check_failed" }, and the page offers Try again. If Cloudflare can't be reached, submissions are refused rather than let through (fail closed).

Self-hosting the pages#

If you run the authentication frontend yourself, Turnstile is configured with two values, set together:

VariableWhereNotes
NEXT_PUBLIC_TURNSTILE_SITE_KEYBuild time (public)Renders the widget.
TURNSTILE_SECRET_KEYRuntime, server onlyVerifies tokens.
TRUSTED_PROXY_HOPSRuntimeProxies that append to X-Forwarded-For: 1 behind Cloud Run alone, 2 behind an external load balancer.

Both unset turns the check off. Only one set is a configuration error: in production, protected submissions are refused (503) rather than run unprotected. For testing, Cloudflare publishes test keys that always pass, always fail, or always ask for a click.