Menu

Developer documentation

Bot protection: edge posture and honest limits

/docs/bot-protection

Status: adopted 2026-08-04.

Signet does not contain a bot-scoring network, JavaScript fingerprinting service, CAPTCHA, or Clerk-style bot-detection switch. Do not describe a plain Signet instance as having bot detection. For a public hosted deployment, put an abuse-aware edge in front of it and keep Signet's own deterministic controls enabled. This guide uses Cloudflare because that is the measured deployment shape; another edge is acceptable only if it preserves the same boundaries.

The layers solve different problems:

layerwhat it can dowhat it cannot prove
Cloudflare edgeabsorb volumetric traffic; apply WAF, rate, and bot signals before origin workaccount identity, credential correctness, or Signet lock state
Signet rate limiterbound requests per source IP and exact auth path; optional PostgreSQL storage coordinates processesidentify bots or absorb traffic before it reaches the origin
Signet account lockoutpersistently bound failed password attempts per canonical identity across IPs/processesdistinguish a bot from a forgetful person
email/disposable policyrefuse configured identity-creation inputsstop automation using acceptable addresses

Edge protection is defense in depth. It does not replace [rate_limit], [lockout], [email_policy], or [disposable_email].

Multi-node Signet deployments should use [rate_limit] storage = "database" as described in the rate-limiting guide. That closes process-local quota splitting; it does not replace an edge because every refused request has still reached the origin and consumed a database operation.

Required Cloudflare boundary

  1. Proxy the authentication hostname in Cloudflare. Prevent a client from reaching the origin around Cloudflare: prefer Cloudflare Tunnel or a per-zone/per-hostname Authenticated Origin Pull; otherwise restrict the origin firewall to current Cloudflare address ranges. A proxied DNS record alone does not close an origin-IP bypass.
  2. At the last trusted proxy before Signet, accept CF-Connecting-IP only from an authenticated or allowlisted Cloudflare source and replace X-Forwarded-For with that one value. Never append an incoming X-Forwarded-For. Signet uses the first value as its limiter identity; preserving a client-supplied first value lets the client choose fresh buckets or charge a victim's bucket.
  3. Preserve the public Host, set [server] base_url to the outer HTTPS origin, and follow the scheme-header rules in the reverse-proxy guide. Bot controls do not repair a broken proxy trust boundary.
  4. Verify from outside the origin network that the public origin works, direct-origin requests fail, and two different clients arrive in different Signet rate-limit buckets.

Cloudflare documents CF-Connecting-IP as the consistent, single-valued visitor address and recommends it over X-Forwarded-For at the origin. This is why the rewrite above is deliberate, not an interchangeable header preference.

Edge rules: start narrow

Match the configured hostname, HTTP method, and exact deployed [server] base_path; the default paths below therefore begin with /api/auth. Confirm real paths in Cloudflare analytics before enforcing a rule.

Start with observation, then rate-limit high-cost unauthenticated writes such as:

  • POST /api/auth/sign-in/email, /sign-in/username, and /sign-up/email;
  • password-reset and verification sends, including /request-password-reset, /send-verification-email, and /email-otp/send-verification-otp;
  • OTP/code verification and anonymous identity creation; and
  • the browser pages /login, /sign-up, and /forgot-password when they are enabled.

Choose thresholds from observed legitimate traffic and NAT/mobile concentration, not by copying a sample number. Prefer counting failed responses when the Cloudflare plan and route response shape make that possible. Keep Signet's lower, deterministic endpoint budgets enabled: edge counters can reset or change independently and Signet's account lockout covers attacks distributed across IPs.

Roll out each rule in this order:

  1. log/observe;
  2. rate-limit or challenge a small browser-only cohort;
  3. verify password, OAuth callback, OTP, reset, mobile, API-client, monitoring, and IPv6 flows; and
  4. expand while watching Cloudflare events plus Signet 429, retry-after, and account.locked telemetry.

Do not turn on a blanket cache rule for the authentication hostname as part of this work. Bypass cache for state-changing auth APIs and callback routes; treat the public docs and OIDC discovery/JWKS documents as separate, deliberately reviewed cache policies.

Challenges are not transparent to API clients

Cloudflare's free Bot Fight Mode is not the default recommendation for an auth hostname. It covers the whole domain, cannot be skipped with WAF custom rules, and Cloudflare warns that it may challenge API or mobile traffic. A better-auth client expects JSON from auth routes; an injected HTML/JavaScript challenge is a protocol failure even when it blocks a bot.

Use narrowly scoped WAF/rate-limiting actions for JSON routes. Use Managed Challenge only where the request is an actual browser navigation/form flow and the complete client matrix has passed. If a deployment needs bot-product exceptions, use a Cloudflare product whose rules can be skipped for the affected API, callback, health, and monitoring paths; do not assume Bot Fight Mode exceptions exist.

Turnstile is also not integrated into Signet. A widget by itself is not protection: Cloudflare requires server-side Siteverify validation, and its tokens are single-use and expire after five minutes. Adding a widget to an application page does not protect clients that call Signet's public JSON endpoint directly. A customer-specific application or edge Worker may validate Turnstile before granting one application action, but that is a separate online integration, requires its own secret/outbound availability, and must not be represented as a Signet capability.

Truthful claim boundary

Allowed:

This deployment places Cloudflare WAF/rate controls in front of Signet and retains Signet's built-in rate limits, persistent account lockout, and email-admission policy.

Sources retrieved 2026-08-04

  • Cloudflare Bot Fight Mode considerations and inability to skip rules: https://developers.cloudflare.com/bots/get-started/bot-fight-mode/
  • Cloudflare WAF rate-limiting guidance for login, credential-stuffing, OTP, and REST endpoints: https://developers.cloudflare.com/waf/rate-limiting-rules/best-practices/
  • Cloudflare visitor-IP header contract: https://developers.cloudflare.com/fundamentals/reference/http-headers/
  • Cloudflare origin protection options: https://developers.cloudflare.com/fundamentals/security/protect-your-origin-server/
  • Cloudflare Turnstile server-side validation contract: https://developers.cloudflare.com/turnstile/get-started/server-side-validation/

Enter to open · Esc to close