Integrating Cloudflare Turnstile with Sitecore Forms

TLDR;
This post shows how you can quickly prevent form spam and to protect sensitive areas of your site using Cloudflare Turnstile, Cloudflare Snippets, and Cloudflare Workers. It's a slick solution that can be deployed very quickly. This specific implementation does make some assumptions and you will certainly want to customize it to your liking.
How Turnstile Works
Turnstile operates similarly to Google's reCAPTCHA, but the difference is that it generally doesn't rely on user interaction or invasive tracking.
When Turnstile does require interaction, the most a user needs to do is click a checkbox, as opposed to confusing and time consuming challenges:
Ways in Which Turnstile is Superior Compared to reCAPTCHA
- Does not track users across sites.
- Does not use Google cookies or store user data.
- Does not send user behavior data to Google.
- Does not introduce additional latency.
- Does not run on Google infrastructure.
- Fewer false positives.
- Challenges are easy for real users to solve.
- Essentially free for most use cases.
- Multiple widget types: managed (potentially interactive), non-interactive, and invisible.
- Can run 100% invisibly, whereas reCAPTCHA has historically had rules about sites needing to indicate that they are using reCAPTCHA. Something in the Terms of Use...
Clarity on Pricing / Features
- On the free plan, invisible widgets get up to 1 million siteverify (server-side) validation requests per month. The managed and non-interactive widgets have unlimited siteverify requests. This was inferred from the general availability blog post.
- You don't need an Enterprise plan to use invisible widgets.
- Cloudflare says that that white label branding is only available on Enterprise plans. What they are referring to specifically are visible widgets without the Cloudflare logo. That holds true when you are displaying widgets, but you can also hide the widgets with CSS or use invisible widgets.
Goals for Integrating Turnstile with Sitecore Forms
If we expect that clients will eventually end up using XM Cloud, we should aim for loose coupling between Turnstile and Sitecore Forms. These would be the requirements:
- Manage all Turnstile functionality at the edge, with no Sitecore changes required whatsoever.
- Automatically inject the widget on all pages containing Sitecore Forms.
- Automatically perform siteverify requests on all Sitecore Forms POST endpoints.
Explanation of High Level Functionality
- A Turnstile widget is configured in Cloudflare. Specify the allowed hostnames, and whether the widget is Managed (visible), Non-interactive (visible), or Invisible.
- Use a Cloudflare Snippet to inject the Turnstile widget into every Sitecore form on every page.
- When the page loads, the injected widget completes a challenge and receives a token from Cloudflare. The token value is automatically stored as a hidden field within the form.
- If the widget can't complete the challenge, display a message to the user and allow them to interact with the widget.
- When the form is submitted, a Worker intercepts requests to the
/formbuilder
endpoint.- If the method is POST, the token is extracted from the form input, and a siteverify request is made.
- If the verification is successful, the form proceeds to submit.
- If unsuccessful, the server returns an error response. Display the error on the client side.
Specifics Regarding Sitecore Forms Integration
There are a number of ways to build Sitecore Forms. Imagine a "Contact Us" form that has two pages. The first page contains the form fields, and the second page displays a "Thank You" message. That is, the submit action on page one is to:
- Save or email the form data
- Display the "Thank You" page
In this scenario, when you submit on the first page, the server responds with pure HTML that replaces the initial <form>
entirely. The same is also true if server-side validation fails; the form is injected back into itself, with additional error-related elements returned by the server.
Happy path implementation of Turnstile is very straightforward. It is the sad paths that pose the greatest challenges; particularly when the siteverify fails.
Sitecore Forms HTML Anatomy
The HTML below is sample output of a form. Notable aspects that are relevant to a Turnstile integration are:
- The form uses several AJAX-related attributes (
data-ajax="true"
,data-ajax-method="Post"
,data-ajax-update
, anddata-ajax-mode="replace-with"
) to submit data asynchronously. This allows parts of the page to update without a full refresh. - The
data-ajax-success
attribute contains inline JavaScript that runs after a successful AJAX request. This code re-initializes client-side validation and additional Sitecore-related trackers and condition parsers, ensuring that interactive behaviors are preserved on content update. - The inline AJAX success callback includes a call to
jQuery.validator.unobtrusive.parse(...)
. This re-binds validation to updated form elements after partial page updates, ensuring that client-side rules (such as required field checks) continue to function. - Each form field includes elements with attributes such as
data-valmsg-for
and classes such asfield-validation-valid
. These elements serve as placeholders where validation error messages can be rendered dynamically. - If the form fails client-side validation (
invalid-form.validate
event), the button's disabled state is removed, allowing users to correct errors and resubmit.
In summary, the aspects we care about are how the form is validated, how it changes when submitted, and how error messages are displayed.
Cloudflare Worker
The Worker proxies Sitecore form submission requests and makes siteverify requests.
Cloudflare Snippet
The Snippet injects the Turnstile script reference, injects widgets, and responds to various states.
Testing
As stated earlier, the happy paths are easy to implement and easy to test. The sad paths are trickier.
Cloudflare Turnstile provides various test keys that can simulate various widget and siteverify states / responses: https://developers.cloudflare.com/turnstile/troubleshooting/testing/
See my previous post on how to disable client-side validation to speed up testing.
Future Updates
- Look into enabling pre-clearance.
- Code refactoring.
- Potentially display error states / messages in a popup so as to not make assumptions about the various styles and structures of forms that this functionality may be enabled on.
Challenge yourself,
MG