I stumbled upon Cloudflare Turnstile when I had to log into my GenerateBlocks account. On previous login attempts, GenerateBlocks would send a code via email that you would then have to enter. Super inconvenient, especially if you can’t access the purchaser’s email account. So I was pleasantly surprised when I saw GenerateBlocks change up their bot protection.
So I did some digging. It turns out the login form is using Cloudflare Turnstile to validate its users.
What is Turnstile?
Cloudflare Turnstile is an alternative to a CAPTCHA, and seems to be competing directly with reCAPTCHA 3. It can be embedded in forms, logins, or any place you need to validate that someone is, in fact, human.
Cloudflare’s CDN already offers its customers a Challenge page if the visitor seems spammy or malicious. This same challenge now comes in widget form without having to use Cloudflare’s CDN. This means that Turnstile is a free solution.
The widget outlays a challenge, often without interaction, for the end user. This challenge is adaptable. Meaning if one challenge is not working well, then the challenge can be updated without any code modifications on your end. So it’s a one and done implementation, in theory.
Opting in to Turnstile
It turns out that if you have a Cloudflare account, you have a new Turnstile tab. Note the beta tag.
From there, you can set up a site that will utilize the Turnstile service.
Once you click on “Add Site,” you’re taken to a screen where you can add a site name, add a domain, and select your widget type.
For my case, I chose the “Non-interactive” version.
Once you choose to “Create” the site, you’ll be shown instructions and receive a Site Key and a Secret Key.
Adding Turnstile to your site
Now here’s the rub. Turnstile is relatively new, so not everything has a solution yet.
For WordPress sites, however, there is a very versatile plugin called Simple Cloudflare Turnstile, which integrates natively with many solutions.
Current Version: 1.20.3
Last Updated: June 1, 2023
I like the idea of a plugin that integrates with many solutions, but I really only cared about one type of implementation. Comments.
Turnstile and WordPress comments
I have a premium plugin called Comment Edit Pro, which integrates with several anti-spam services such as reCAPTCHA 3 and Akismet.
Since I was so impressed with Turnstile and how it worked, I wanted to add a native integration specific to comments. Yes, the plugin above does comments, but since I already integrated reCAPTCHA 3 into Comment Edit Pro, I felt Turnstile needed to be added too.
So that takes me back to square one. I needed to develop my own solution.
On the Turnstile Getting Started page, you’re shown a script you can include that can run in the
You’re then shown steps for the client-side (user-facing) integration. You can choose between Explicit and Implicit rendering.
An implicit render is more of a hands-off approach, hence the name implicit. You just need to add a sliver of HTML to your form.
But first you’ll need to add the Turnstile script to your page.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>Code language: HTML, XML (xml)
After that, find your form’s source and add in the HTML.
<form method="POST" action="/handler"> <!-- form inputs and fields --> <div class="cf-turnstile" data-sitekey="1x00000000000000000000AA"></div> <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> </form>Code language: HTML, XML (xml)
Just add a
div with class
sitekey data attribute.
Turnstile will then inject a hidden
input element named
cf-turnstile-response, which will contain a token if a challenge is passed.
<input type="hidden" name="cf-turnstile-response" id="cf-chl-widget-mgegk_response" value="XXXX.DUMMY.TOKEN.XXXX">Code language: HTML, XML (xml)
Once the form is submitted, you’d check for the
cf-turnstile-response post variable on the server-side, validate it against a Turnstile endpoint, and either complete or reject the form.
Explicit rendering, as in, you are actively doing something, is a more hands-on approach. You’ll be manually rendering Turnstile as opposed to the widget being shown automatically.
The script tag is the same except for a callback function when the Turnstile script is done initializing.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" async defer></script>Code language: HTML, XML (xml)
In this example, there is an
onload callback named
You can name this callback function anything you like, but if you have
The example Turnstile provides for explicitly rendering the widget is:
Not shown is any server-side validation (we’ll get to that).
Since this is “explicit”, we’ll have to render our own widget, get the token, and then validate it.
You can also pass in additional parameters to
turnstile.render to customize the appearance and output.
Validating the response on the server
Once your token is read in via a form variable, you’ll need to capture that on the back-end (server-side validation).
Here’s a quick example of doing that in WordPress land:
To validate on the server, you need the Turnstile secret key and the client-side token.
Since Turnstile adds the
cf-turnstile-response hidden variable in the form, you can just check the
$_POST super global for its existence.
The endpoint to verify the token is:
body of the request should contain keys:
response. Secret being your Turnstile secret key, and response being the token.
If the response is successful, it’ll pass a
success parameter in its
That’s pretty much it for server validation. For now.
Comment Edit Pro and Turnstile
For Comment Edit Pro, the first thing I needed was a UI to capture the site key and secret key.
I also added in some options to configure the Turnstile widget appearance.
The first dilemma with these options was how to get this working locally. If you remember, when enabling Turnstile for a site, you have to give an actual working domain. So how do I test if it’s working and not have to edit files directly on the server?
There are test keys. Buried in the Turnstile FAQ are the local keys you need to test with. I’ve listed them here as well.
|Forces an interactive challenge|
|Yields a “token already spent” error|
Armed with these test keys, I was ready to begin the process of explicitly setting the widget and doing the server-side validation.
Initializing the actions and filters
I had to use the
wp action as it loads pretty late and I needed a reliable mechanism for determining if comments were open or not.
action_wp callback, I do some sanity checks to make sure that comments are open, and to ignore Turnstile for those who can moderate comments. I then load in my options, and check to see if Turnstile should be enabled for regular logged-in users.
Modifying the Submit button
For Turnstile, I needed to get the comment submit button’s HTML ID attribute value. This is so I can target the button and work backwards to get the form it’s contained in. I hooked into the
comment_form_submit_button filter to do this.
Within the callback, I check to see if Turnstile is active and replace the comment submit button’s ID with
Loading the frontend scripts
The next actions and filters involve loading the Cloudflare frontend script.
Let’s start with callback
Lastly, I use
wp_localize_script to output the widget options that were saved in the admin. This is so I can set them on the frontend.
Up next is deferring the Cloudflare script.
A simple search/replace gets the job done here.
Adding the widget container to the comment section
comment_form_defaults filter, I can modify the output of the comment section and inject my empty widget container.
The frontend script
I attempt to get the Submit button (we set the ID earlier), and I then trace backwards to get the form, so that I can then get the textarea field.
I need the textarea field because the tokens have a shelf-life of 300 seconds. I want to only initialize Turnstile if someone is actively writing a comment.
When a user starts typing into the textarea field, the widget will begin to be rendered. I did this because of the token timeout.
Comments are typically at the end of the post, so starting the widget on page load didn’t make sense in this case. The timeout of 300 seconds is so that I can refresh the widget and generate a new token.
The timeout is shown in this highlighted code section.
turnstile.reset will generate a new token and will call the
callback again with this token.
The Frontend output
If all is well, a user just has to start typing in the textarea field for Turnstile to be initialized.
Armed with a token courtesy of the injected hidden input from Turnstile, I can do server validation when the comment is submitted.
Back to PHP land, I hook into the action
pre_comment_on_post to make sure that we reject a comment early.
And here’s my server-validation method in all its glory.
In this developer diary, I went over what Cloudflare Turnstile is, why or not you should use it, and how to implement it.
I also covered how I added the feature to comments. In fact, it’s enabled below if you want to give it a whirl. As mentioned, the widget doesn’t initialize until you start typing a comment.
Thank you so much for reading.
2 thoughts on “Developer Diaries: Comments and Cloudflare Turnstile”
Nice article, Ron.
Does this service function as a replacement for Google’s recaptcha? I like the idea of one less Google service hoovering up data about me.
Sure does! It’s a lot easier to implement too.