Add an optional provider check to password sign-in and email-link requests.
Before you start
Without captcha configuration, password and email sign-in work without a provider widget. To enable one, create provider keys for your app’s hostname and store the secret key in your host’s secret store. This page describes the generated integration; verify it with your real provider keys before deployment.
Cloudflare Turnstile
bin/rails generate latchkey:challenge turnstileConfigure these environment variables with your own values:
TURNSTILE_SITE_KEY=your-public-site-key
TURNSTILE_SECRET_KEY=your-private-secret-key
TURNSTILE_ALLOWED_HOSTNAMES=accounts.example.comThe generated initializer checks responses on the server and applies the challenge to sign-in and email-link requests. Use a comma-separated hostname list when needed. Turnstile setup documentation covers provider-side configuration.
Google reCAPTCHA
bin/rails generate latchkey:challenge recaptcha --version=v2Use RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET_KEY and RECAPTCHA_ALLOWED_HOSTNAMES. Match your provider keys to the chosen v2/v3 mode. See Google’s v2 configuration guide.
Configuration and outages
In production, missing keys or an explicit hostname allowlist prevent the generated provider initializer from booting. Outside production, missing keys leave the provider disabled with a warning.
A rejected check and an unavailable provider are different outcomes. The default outage policy is :closed, which blocks protected actions when the provider cannot be verified. config.challenge_when_unavailable = :open allows them during an outage and is a security tradeoff to evaluate deliberately.
Verify the integration
- Run
bin/rails latchkey:doctor. - Test password sign-in and an email-link request with real keys for the configured hostname.
- Exercise a rejected response and provider unavailability in a safe test environment.
- Disable JavaScript and confirm that policy is enforced with an understandable unavailable state. Do not bypass captcha to simulate no-JavaScript support.
Add :reauthenticate to challenge_on if sign-out-everywhere should also require the provider check.