Bot protection with reCAPTCHA
Self-service flows are reachable without a session, which makes them the flows a bot reaches first. SecureAuth CIAM can put a Google reCAPTCHA check in front of them, so an automated client is stopped before it consumes an activation code or triggers a message send.
The check runs on every self-service flow a bot can reach without a session:
- Activation links. The check happens before the activation code is consumed, so a link that fails it stays usable.
- Credential reset, for passwords, TOTP, and passkeys.
- Device pairing.
- Self-registration.
How the check behaves
The gate uses reCAPTCHA v3 first. v3 is invisible: it scores the client in the background without asking the user to do anything. Most users never see a challenge.
What the check does depends only on which key pairs are configured:
- Both v3 and v2. v3 runs first, invisibly. When a score falls below the threshold, or reCAPTCHA rejects the response outright (a reused or expired token), the user gets the v2 checkbox instead. A second invisible check would score the same client the same way, so the checkbox is the only thing that lets a real person through.
- v2 only. The checkbox is shown directly, with no invisible pre-check.
- v3 only. There is no checkbox to fall back to. A client whose score is too low is refused, because a second invisible check would reach the same verdict. A reused or expired token is different: the client was never the problem, so it gets a fresh invisible check.
Configure both pairs unless you have a reason not to: v3 alone turns a low score into a hard failure, and v2 alone shows every user a checkbox.
Configuration
The check is controlled entirely by the deployment captcha configuration. Two things must be true for it to run:
| Setting | Where | Notes |
|---|---|---|
captcha.enabled | Platform configuration | Turns reCAPTCHA on for the deployment. |
| At least one key pair | Platform configuration | A v2 pair (site_key / secret_key), a v3 pair (site_key_v3 / secret_key_v3), or both. |
There is no per-tenant feature flag: if captcha is enabled with a usable key pair, the check
runs on every flow above. captcha.threshold is the minimum v3 score a client must reach,
from 0.0 to 1.0, defaulting to 0.5. Raising it challenges more users with the checkbox;
lowering it lets more automated traffic through. See the
platform configuration reference.
If captcha.enabled is true but no usable key pair is configured, the check does not run and
the flows continue unprotected. The activation flow logs a warning in that state; the other
flows do not.
Exempting addresses from the check
captcha.whitelist takes a list of address suffixes that skip verification entirely, which
is useful for automated testing against a deployment that has the gate on:
captcha:
whitelist:
- "@example.com"
On an activation link the suffix is matched against the address the code was issued to, not against anything the caller submits, so an exemption cannot be claimed for someone else's link.
Credential reset, device pairing, and self-registration have no such issued address to match against - the caller types the identifier - so there the suffix is matched against the submitted value, and anyone who submits a whitelisted address skips the check. Keep the whitelist empty on any deployment where those flows are reachable by the public.
Protecting activation links from email scanners
Email security scanners follow links in messages before the recipient does. On an activation link that means the activation code is consumed before the user ever clicks, and they arrive to find the link already spent.
reCAPTCHA is what stops this. The check runs on the initial GET, so a scanner that follows the link gets the interstitial instead of an activated account, and the code stays usable for the real recipient.
Configure reCAPTCHA on any deployment that sends activation links by email. It is the only
protection against scanner-consumed activation codes. With captcha.enabled off, or on with no
usable key pair, an activation link is consumed by whatever fetches it first.
Two earlier tenant feature flags covered parts of this and have been removed:
two_step_activation, an interstitial that auto-submitted so a GET alone could not activate,
and activation_bot_protection, which rejected requests by User-Agent. Both were already
skipped whenever reCAPTCHA was configured.
Values already stored for either flag are ignored and need no cleanup. Setting them is a
different matter: the feature endpoints validate the flag name, so a request that enables or
disables either one is now rejected with an unknown flag error. Drop them from any
provisioning or automation that still sets them.