Cloudflare Turnstile (CAPTCHA) Quick Setup on a Rails app

Cloudflare Turnstile (CAPTCHA) Quick Setup on a Rails app

As I was creating a landing page for a business, I had to add a contact form that immediately started to receive spams. To mitigate this, I decided to take a look into CAPTCHA systems and landed on two: Google's reCAPTCHA and Cloudflare's Turnstile.

Google's reCAPTCHA seemed to be slightly more cumbersome to implement even having the great recaptcha gem available for Rails. The reason being that there are 2 distinct versions of the captcha system that you need to create in the Google's reCAPTCHA website (v2 is apparently used as a fallback to v3) and, no only it is a bit a confusing (at least to me), it displays a little flag on the bottom right corner of the screen which is quite annoying:

Then, I decided to take a closer look into Cloudflare's Turnstile. Right off the bat, it is WAY more straightforward to implement. After creating an account on their website, it will generate a key for the frontend and a secret key for the backend.

Here's how a simple implementation can go. On your frontend, there are two steps:

  1. Add their script into your page

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" defer></script>

  1. Inside your form, add a div with the specific CSS class of cf-turnstile and a data attribute of data-sitekey="YOUR_CLIENT_SIDE_KEY"

<div class="text-center cf-turnstile" data-sitekey="<%= ENV["CLOUDFRONT_TURNSTILE_SITE_KEY"] %>"></div>

On the backend, wherever you handle the form action triggered when you submit the form, make a POST request to Cloudflare's site verify endpoint. If you use Rails and the HTTParty gem:

idempotency_key = SecureRandom.uuid

response = HTTParty.post(
  "https://challenges.cloudflare.com/turnstile/v0/siteverify",
  body: {
    secret: ENV["CLOUDFRONT_TURNSTILE_SECRET_KEY"],
    response: params["cf-turnstile-response"],
    remoteip: request.remote_ip,
    idempotency_key: idempotency_key
  }
)

result = response.parsed_response
return result["success"] == true && result["idempotency_key"] == idempotency_key

The cf-turnstile-response is a parameter that Cloudflare injects into your form upon submission and the idempotency_key is an arbitrary value that you can verify to assert that the challenge request matches the response you got.

And that's it! You can further add this to a method like verify_turnstile and if that returns true you can carry on with the usual logic (e.g. send email, register user, etc).