Skip to content

Bot and abuse protection

The first-party login surface is gated by a policy built on signals the edge attaches to every request — country, bot score, geolocation — plus an interactive challenge. The outcome is one of three actions.

ActionResult
allowProceed.
challengeA valid Turnstile token is required to continue.
blockRefused outright with 403 login_blocked.

Country. Two configurable lists: countries that are blocked outright, and countries that must pass a challenge. Blocking wins over challenging.

Bot score. Where the zone has bot management, a score from 1 (definitely automated) to 99:

ScoreAction
< 10Block
10–29Challenge
≥ 30Allow

Verified bots — declared, well-behaved crawlers — are exempt from scoring entirely, so a legitimate crawler is not treated as an attacker.

A challenged request gets a 403 naming the challenge and the site key to render:

{
"error": "challenge_required",
"challenge": "turnstile",
"site_key": "0x4AAA…"
}

Retry the login call with the resulting token:

POST /login/email/start
{ "email": "ada@acme.com", "turnstile_token": "" }

Tokens are verified server-side against the challenge provider, including the client IP.

Configure Turnstile before enabling any challenged country, or logins from those countries will fail in production.

Throwaway-inbox domains are refused at social just-in-time signup, matching both the domain and one level of subdomain nesting. This blunts scripted signup, but it is a filter, not a defence — rate limits and challenges are what actually hold the line.

After a successful login, two signals are recorded without gating the login:

  • new_device — a first-ever device for this user. The device fingerprint is derived from the user agent and accepted languages, deliberately excluding IP and geolocation so that a user who travels is not flagged as a new device every trip.
  • impossible_travel — the distance from the previous login’s location implies a speed above 900 km/h, faster than a commercial flight.

Both land as login_anomaly audit events, and therefore reach any SIEM stream you have configured.

They are recorded rather than enforced on purpose. Whether a new device warrants a step-up prompt, a notification email, or nothing at all is a product decision, and the data is where you can act on it.

The login buckets are the tightest on the platform: 10 per minute per IP for one-time codes and magic links (which also caps outbound email), 30 for passwords. Password failures are additionally capped per email address at ten attempts, so grinding one account trips the account lockout rather than a generic IP throttle. See errors and rate limits.

EventWhen
login_blockedA request was refused by policy, with the reasons and country.
login_challengedA challenge was required and not satisfied.
login_challenge_skippedA challenge was needed but unavailable — includes whether it failed closed.
login_anomalyA post-login signal fired.

Alert on login_challenge_skipped with fail_closed: true: it means real users are being turned away because the challenge provider is not configured.