Designly Blog

Turnstile:  CloudFlare's New reCaptcha Replacement in NextJS / React

Turnstile: CloudFlare's New reCaptcha Replacement in NextJS / React

Posted in Full-Stack Development by Jay Simons
Published on March 9, 2023

CAPTCHA (Completely Automated Public Turing Test to Tell Computers and Humans Apart) is a type of challenge-response test that is used to determine whether or not the user of a website is human or an automated bot. CAPTCHAs are designed to be difficult for machines to solve, while still being easy enough for humans to understand. CAPTCHAs usually involve asking the user to perform a task that is easy for a human, but difficult for a computer, such as identifying letters or numbers in a distorted image, or completing a simple puzzle. By using CAPTCHAs, websites can prevent automated bots from accessing their content or services, which can help to reduce spam, prevent fraudulent activity, and protect user data.

There are several types of CAPTCHAs available, including image-based CAPTCHAs, audio-based CAPTCHAs, and even gamified CAPTCHAs that require users to complete a simple game. While CAPTCHAs can be effective at preventing automated bots from accessing websites, they can also be frustrating for users, particularly for those who have visual or hearing impairments. As such, many websites have begun to explore alternative methods of verifying user identities, such as biometric authentication, behavioral analysis, and two-factor authentication.

In the past, I used Google's reCaptcha service for many years, and then I heard about Google myriad privacy issues. Check out this PC Mag Article for more information. I then switched to hCaptcha, but I think many users find it annoying.

In response to Google's reign of CAPTCHA tyranny and hCaptcha's annoyingness, CloudFlare developed their own mostly passive CAPTCHA system called Turnstile, which is convenience and privacy-focused. Unfortunately, the service is still new, and there's not a lot of information regarding the efficacy of Turnstile over its competitors, but I mean, it's CloudFlare... They're the gods of Internet security! And, as I've said many times, unless you're a target for a major bot network attack of your web forms (almost certainly not), then I'm sure it's going to be fine for 99.999% of websites.

Also, I might add that CloudFlare offers this service 100% free to anyone with a free CloudFlare account. Kudos to CloudFlare for continuing to make the Internet a better and safer place.

In this article, I'm going to show you how to implement CloudFlare's Turnstile CAPTCHA service in a React or NextJS app using Le0Developer's react-turnstile package. It's a simple library, and I seriously weighed against just writing my own code, but he did such a nice job and everything is typed using CloudFlare's typings. It's just well written code in general.

To start, you'll need to create a new Turnstile site profile on CloudFlare. Log your dashboard and click Turnstile on the left sidebar. This option is under the main options, not under a specific website (if you have any hosted). Next, click Add site. Enter a Site name and Domain(s) and then I would recommend you select Managed for widget type. I've done some testing and it does an automatic test almost every time unless you refresh the page a bunch of times or do other weird stuff during development, in which case you'll simple get a checkbox.

Add new Turnstile site
Add new Turnstile site

Next, you should get a confirmation screen. Copy your Site key and Secret key to a secure location. You'll need to import these in your env.

Copy your keys
Copy your keys

Now in your React project, add the following lines to your .env:

TURNSTILE_SITE_KEY=your_site_key
TURNSTILE_SECRET_KEY=your_secret_key

Next, you'll want to install react-turnstile:

npm i react-turnstile

Now you can use this component in your form as such:

// @/components/TurnstileFormExample.js

import React, { useState } from 'react'
import Turnstile from "react-turnstile";

const initialFormData = {
    fullName: '',
    token: ''
}

export default function TurnstileFormExample() {
    [formData, setFormData] = useState(initialFormData);

    const setValue = (k, v) => {
        setFormData((oldData) => (
            {
                ...oldData,
                [k]: v
            }
        ))
    }

    const handleSubmit = async () => {
        const endpoint = "/api/form-handler";
        const submitData = new FormData();
        for (const k in formData) {
            submitData.append(k, formData[k]);
        }
        try {
            const result = await fetch(url, {
                body: submitData,
                method: 'post'
            });

            const outcome = await result.json();
            console.log(outcome);
        } catch (err) {
            console.error(err);
        }
    }

    return (
        <form>
            <input
                type="text"
                name="fullName"
                placeholder="Full Name"
                onChange={(e) => setValue('fullName', e.target.value)}
            />
            <Turnstile
                sitekey={process.env.TURNSTILE_SITE_KEY}
                onVerify={(token) => setValue('token', token)}
            />
            <button type="button" onClick={handleSubmit}>Submit</button>
        </form>
    )
}

And on the API route:

// @/pages/api/form-handler.js

export default async function formHandler(req, res) {
    return checkTurnstileToken(req, res);
}

async function checkTurnstileToken(req, res) {
    const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
    const token = req.body.token;

    const formData = new FormData();
    formData.append('secret', process.env.TURNSTILE_SECRET_KEY);
    formData.append('response', token);

    try {
        const result = await fetch(url, {
            body: formData,
            method: 'POST',
        });

        const outcome = await result.json();
        if (outcome.success) {
            return processForm(req, res);
        }
    } catch (err) {
        console.error(err);
    }
    res.status(500).json({ message: "Failed to validate CAPTCHA response" });
    return;
}

async function processForm(req, res) {
    // Continue processing the form
}

That's it. Enjoy secure and hassle-free automated CAPTCHA on all your forms and login pages!

Thank you for taking the time to read my article and I hope you found it useful (or at the very least, mildly entertaining). For more great information about web dev, systems administration and cloud computing, please read the Designly Blog. Also, please leave your comments! I love to hear thoughts from my readers.

Looking for a web developer? I'm available for hire! To inquire, please fill out a contact form.

Further Reading:

  1. Announcing Turnstile, a user-friendly, privacy-preserving alternative to CAPTCHA - CloudFlare Blog
  2. Replace CAPTCHAs with Private Access Tokens - Apple Developer
  3. Cloudflare Turnstile vs hCaptcha - xenForo Forum

Loading comments...