Captcha
WIPVerifying Cloudflare Turnstile, hCaptcha and reCAPTCHA v3 tokens through auth.captcha, and where to put the check
auth.captcha verifies a captcha token server-side. Configuring one makes it available and does nothing else: no flow or route calls it, so you put the check in front of whatever needs it.
import { authTurnstileVerifier } from '@gentleduck/auth/core'
const auth = createAuth({
// ...
captcha: authTurnstileVerifier({ secret: process.env.TURNSTILE_SECRET ?? '', expectedHostname: 'app.example.com' }),
})
app.post('/auth/signup', async (req, res) => {
const solved = await auth.captcha.verify({ token: req.body.captchaToken, remoteIp: req.ip })
if (!solved.success) throw new AuthError('AUTH_INVALID_PARAMETERS', { detail: 'captcha' })
// ... create the account
})
| Verifier | Extra options |
|---|---|
authTurnstileVerifier | expectedAction |
authHCaptchaVerifier | none; hCaptcha echoes no action |
authRecaptchaV3Verifier | minScore (0.5 by default), expectedAction |
authNullCaptchaVerifier | Passes everything, for tests. It refuses to construct under NODE_ENV=production. |
Every siteverify verifier takes secret, expectedHostname (one or a list), maxChallengeAgeMs (5 minutes by default), timeoutMs (5 seconds), and endpoint to override the provider URL, which must be a public https host.
The result
verify never throws. A rejected token, a timeout, a network failure and an unreadable response all answer { success: false, errorCodes }, so a captcha outage cannot turn sign-up into a 500. errorCodes mixes the provider's own codes with duck-auth's, such as timeout, hostname-mismatch, action-mismatch, challenge-expired and score-too-low. The result also reports score, hostname, action and challengeTs, for a caller that wants its own rule.
Without a captcha in the config, auth.captcha fails every call with captcha-not-configured rather than being undefined.
strict({ env: 'production' }) refuses the null verifier and a plain-http endpoint.