Protect payment forms from malicious bots using Turnstile
This tutorial shows how you can build a more secure payment form using Turnstile. You can learn how to block bot access on the checkout page and trigger additional authentication flows by integrating Turnstile with Stripe.
- Sign up for a Cloudflare account ↗.
- Install
Node.js
↗.
Node.js version manager
Use a Node version manager like Volta ↗ or nvm ↗ to avoid permission issues and change Node.js versions. Wrangler, discussed later in this guide, requires a Node version of 16.17.0
or later.
- Sign up for a Stripe ↗ account.
First, you will need to prepare a Cloudflare Turnstile widget to use for this application.
- Log in to the Cloudflare dashboard ↗ and select your account.
- Go to Turnstile and create a new Turnstile widget.
- Copy the sitekey and the secret key to use in the next step.
Now that your Turnstile widget it ready to use, you can create your Worker application.
C3 (create-cloudflare-cli) ↗ is a command-line tool designed to help you set up and deploy new applications to Cloudflare. In addition to speed, it leverages officially developed templates for Workers and framework-specific setup guides to ensure each new application that you set up follows Cloudflare and any third-party best practices for deployment on the Cloudflare network.
To efficiently create and manage multiple APIs, let's use Hono
↗. It is lightweight and allows for the creation of multiple API paths, as well as efficient request and response handling.
Open your command line interface (CLI) and run the following command:
If this is your first time running the C3
command, you will be asked whether you want to install it. Confirm that the package name for installation is create-cloudflare
and answer y
.
Additionally, you need to install the create-hono
package.
During the setup, you will be asked if you want to manage your project source code with Git
. It is recommended to answer Yes
as it helps in recording your work and rolling back changes. You can also choose No
, which will not affect the tutorial progress.
Finally, you will be asked if you want to deploy the application to your Cloudflare account. For now, select No
and start development locally.
If you see a message like the one below, the project setup is complete. You can open the secure-payment-form
directory in your preferred IDE to start development.
Cloudflare Workers applications can be developed and tested in a local environment. On your CLI, change directory into your newly created Workers and run npx wrangler dev
to start the application. Using Wrangler
, the application will start, and you'll see a URL beginning with localhost
.
You can send a request to the API using the curl
command. If you see the text Hello Hono!
, the API is running correctly.
So far, we've covered how to create a Worker project using C3
and introduced the open source Hono
framework that streamlines web-application development with Workers.
At the next step, we need to update the Hono project for supporting to web application.
Since we will use JSX to dynamically create HTML, you will need to change src/index.ts
to src/index.tsx
.
At the same time, change the filename specified in the wrangler.toml
.
To connect your web application to both Stripe and Turnstile, you must register the necessary API keys for Stripe and Turnstile as environment variables within your application.
You can obtain test site keys and secret keys for Turnstile from the Turnstile documentation.
Get the publishable key and secret key for Stripe from the Stripe dashboard ↗.
Then, place each key into the .dev.vars
file like the following:
After that, you can generate TypeScript type definition by running the npm run cf-typegen
command.
In local development using Hono and Wrangler, you can retrieve values set in .dev.vars
like this:
Now we are ready for application development. In the next steps, we will develop a payment form using Turnstile and Stripe.
Start by creating a form that uses Turnstile to detect bot access. Add the following code to src/index.tsx
to create a simple form:
Add JavaScript code to our application to implement bot detection using Turnstile. By adding this implementation, the order form submission process will be disabled until the Turnstile bot detection process is completed and it is confirmed that the access request is not from a bot.
This will load the Turnstile script file with a script
tag. The _turnstileCB
function is executed when the script file loading is complete, triggered by the onload=_turnstileCB
in the query string.
In the _turnstileCB
function, turnstile.render()
is executed. The callback
set here removes the disabled
attribute from the submit button
of the form
.
This implementation blocks order operations for any requests that Cloudflare identifies as being made by a bots.
To integrate Turnstile with a Stripe payment form, first you will need to install the Stripe SDK:
Next, implement the code to create a payment form in src/index.tsx
. The following code creates a Payment Intent ↗ on the server side:
Then, add the following code to display the payment form. Edit src/index.tsx
:
The payment form is now ready. To experience how it behaves when a bot is detected, change .dev.vars
as follows:
If you restart the application now, you will notice that you cannot submit the payment form.
This way, you can block requests that use bots to try and manipulate the payment form, such as card testing attacks.
By verifying whether the turnstileToken
is set by the callback
of turnstile.render()
, you can use Turnstile's result when processing the form
's submit
event.
Next, add a step to verify that the token generated by the Turnstile widget is valid and not forged.
In this case, we'll add an API that performs additional validation and server-side processing based on the result of turnstile.render
.
For easier testing, remove the disabled
attribute from the button
tag:
Next, add an API for server-side verification. Please add the following code to src/index.tsx
.
This API validates the Turnstile token generated by the client application and incorporates the result into Stripe's Payment Intent.
Then, add the process to call the created API.
By executing this before calling Stripe's JavaScript SDK in the form's submit event, we can decide whether to proceed with the payment based on the server-side validation result:
By adding this step, we now perform a two-stage check using Turnstile before the payment process.
Since we're saving the Turnstile authentication result in the Stripe data, it is also easier to investigate if a user reports a payment failure.
If you want more strict control, you could add a process to invalidate the Stripe Payment Intent if authentication fails in the POST /pre-confirm
API.
In online payments, it is necessary to protect applications from bot attacks such as card testing and DDoS attacks.
While payment services like Stripe are increasingly implementing bot prevention measures, adding Turnstile can provide an extra layer of security for your payment forms.