Captchas with Cloudflare turnstile

cloudflare turnstile

Everbody loves captchas.

text captcha

These are horrible and can be vague enough that they’re easy to get wrong.

Or how about the image captchas

image captcha

Is it a car or a bit of a car, do I still click on it?

These are also annoying.

checkbox captcha

Are you sure?

Captcha stands for “Completely Automated Public Turing test to tell Computers and Humans Apart”.

They are used to detect bots and stop them performing actions, usually form submissions on web sites.

A bot? is automated software that runs across the web indexing pages (like a search engine), scraping for data or vulnerabilities, a bot could be doing any number of things.

If you run a website of any kind and look at the logs you will see attempts to make requests to popular paths like the wordpress login page.

Captchas help keep spam form submissions under control, although they are not bulletproof, there are apis available that can bypass captchas automatically.

From a users perspective they are not fun to use, can be difficult to pass particularly for accessibility users.

Cloudflare turnstile

Is a new type of captcha, and works like:

“First, we run a series of small non-interactive JavaScript challenges gathering more signals about the visitor/browser environment. Those challenges include, proof-of-work, proof-of-space, probing for web APIs, and various other challenges for detecting browser-quirks and human behavior. As a result, we can fine-tune the difficulty of the challenge to the specific request and avoid ever showing a visual puzzle to a user. Turnstile also includes machine learning models that detect common features of end visitors who were able to pass a challenge before. The computational hardness of those initial challenges may vary by visitor, but is targeted to run fast.”

It is a non interactive challenge, meaning the user doesn’t do anything other than maybe clicking on a check box.

A big win for user experience and accessibility.

Cloudflare provides analytics on successful and unsuccessful challenges.

turnstile analytics

Adding it to a clients contact form has caught pretty much all bot entries after seeing hundreds over a period of a few days.

Cloudflare offers unlimited use for up to 10 domains.

Implement

During setup you are asked to add the domain name you will be using it on, and the widget mode, you can chose managed which decides if interaction is necessary which would be clicking a checkbox, non-interactice mode which shows a small loading animation or a invisible challenge.

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

Add the js script to the head of your page.

<div
  class="cf-turnstile"
  data-sitekey="yourSitekey"
  data-callback="javascriptCallback"
></div>

Turnstile works by looking for an element with a class cf-turnstile and generating a pass or fail token on the client, it then inserts a hidden form element with the name cf-turnstile-response and the code as the value.

<div
  class="cf-turnstile"
  data-sitekey="yourSitekey"
  data-callback="javascriptCallback"
></div>

There is no javascript required assuming your submitting a form, or you can use the javascript callback to handle the token.

You must pass and validate the generated token to the backend, checking the client is not enough, validating the token ensures successful tokens cannot be reused.

Here is how to validate the token with the siteverify endpoint.

func ValidateTurnstile(token string) bool {
	endpoint := "https://challenges.cloudflare.com/turnstile/v0/siteverify"
	secretKey := os.Getenv("TURNSTILE_SECRET_KEY")

	// Prepare form data
	data := url.Values{}
	data.Set("secret", secretKey)
	data.Set("response", token)

	// Make POST request
	resp, err := http.Post(endpoint,
		"application/x-www-form-urlencoded",
		strings.NewReader(data.Encode()))
	if err != nil {
		return false
	}
	defer resp.Body.Close()

	// Read response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return false
	}

	// Parse response
	var result TurnstileResponse
	if err := json.Unmarshal(body, &result); err != nil {
		return false
	}

	fmt.Println(result)

	return result.Success
}
© 2024 Timney.