Google reCAPTCHA new pricing will be rolled out on August 1st, meaning that you have a few days left to migrate to a cheaper alternative or ensure your bank account is well-funded.
Starting at $1 for 1,000 verifications, it is going to cost a lot. At Mailmeteor, we use reCAPTCHA extensively to protect our services from bots. With Google's pricing change, we calculated that we're about to pay thousands of dollar per month to keep using their reCAPTCHA service.
What's a CAPTCHA?
CAPTCHAs are an essential part of the web. It aims to separate good citizens from bad actors. Essentially, it's a service that will operate on the frontend and generate a token that is transmitted to the backend. The backend then verifies that the token is legit, and, if so, performs the action.
Google did a great job at promoting their own service, but thankfully, there are some alternatives:
- hCaptcha. We considered it at first, but their pricing is quite similar to new Google's pricing.
- Cloudflare Turnstile. We are huge fans of Cloudflare, and definitely looked into it. As for now, it's a free service.
Let's dig in.
Moving away from Google reCAPTCHA...
One of our free tools is an AI Email Writer. It's basically an HTML page that send request to our backend, which then makes to a third-party AI solution.
To protect it from abuse, Google reCAPTCHA was enabled from day one. Here's how the verification was done so far (backend-side):
// index.js
app.post('/api/email-ai-writer', recaptcha.middleware.verify, aiEmailWriter)
// ai_email_writer.js
async function aiEmailWriter(request, response) {
try {
// Recaptcha
if (!request.recaptcha || request.recaptcha.error || !request.recaptcha.data) {
console.warn('Recaptcha: verification failed.')
return response.status(403).send({ error: true, message: 'Recaptcha: verification failed' })
} else if (request.recaptcha.data.action !== 'aiemailwriter') {
console.warn('Recaptcha: bad action name')
return response.status(403).send({ error: true, message: 'Recaptcha: bad action name' })
} else if (request.recaptcha.data.score < 0.3) {
const score = request.recaptcha.data.score
console.warn(`Recaptcha: score is below 0.3 (${score})`)
return response.status(403).send({ error: true, message: 'Recaptcha: score too low' })
}
...
That's quite simple and it's an essential part of why Google reCAPTCHA was so popular. The footprint is very limited and it's really easy to implement. For the most curious ones, we leveraged the express-recaptcha package to make it really easy to implement.
... to Cloudflare Turnstile
When migrating to Turnstile, we couldn't found an NPM package, so we had to write a middleware to process the token. Here's how it looks like:
// middlewares/turnstile.js
const turnstile = async (request, response, next) => {
try {
// Turnstile injects a token in "cf-turnstile-response".
const token = request.query['cf-turnstile-response']
const ip = request.header('CF-Connecting-IP')
if (!token) {
throw new Error('Missing CloudFlare Turnstile Token')
}
// Validate the token by calling the
// "/siteverify" API endpoint.
const formData = new FormData()
formData.append('secret', process.env.CLOUDFLARE_TURNSTILE_SECRET_KEY)
formData.append('response', token)
if (ip) formData.append('remoteip', ip)
const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
const result = await fetch(url, {
body: formData,
method: 'POST',
})
// Process the verification outcome
const outcome = await result.json()
if (!outcome.success) {
throw new Error('CloudFlare Turnstile declined the token')
}
request.turnstile = outcome
// If authentified, go to next middleware
next()
} catch (err) {
console.error(err)
return response.status(403).send('Forbidden')
}
}
export { turnstile }
Once the middleware is in place, we can apply it to any requests:
// index.js
app.post('/api/ai-email-writer', aiRateLimiter, turnstile, aiEmailWriter)
And inside the function that treats the request, it's quite similar to what we had previously:
// ai_email_writer.js
async function aiEmailWriter(request, response) {
try {
// CloudFlare Turnstile protection
if (!request.turnstile || request.turnstile.error) {
console.warn('Recaptcha: verification failed.')
return response.status(403).json({ error: true, message: 'Recaptcha: verification failed' })
} else if (request.turnstile.action !== 'aiemailwriter') {
console.warn('Recaptcha: bad action name')
return response.status(403).json({ error: true, message: 'Recaptcha: bad action name' })
}
...
Conclusion
Migrating from reCAPTCHA to Turnstile is straightforward and shouldn't take more than a few hours. It works quite similar and will definitely save you a lot of money at the same time.
I didn't cover the frontend in this article, because we use an invisible widget that our users don't see. But Turnstile's documentation covers extensively how to use their interactive widgets.
Call it a day!
Top comments (0)