Skip to content

Commit 15124ee

Browse files
update the Turnstile tutorial
1 parent 628c852 commit 15124ee

File tree

1 file changed

+104
-106
lines changed

1 file changed

+104
-106
lines changed

src/content/docs/turnstile/tutorials/protecting-your-payment-form-from-attackers-bots-using-turnstile.mdx

Lines changed: 104 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Protecting your payment form from attackers' bots using Turnstile
2+
title: Protect payment forms from malicious bots using Turnstile
33
pcx_content_type: tutorial
44
updated: 2024-11-11
55
difficulty: Beginner
@@ -16,34 +16,33 @@ spotlight:
1616
author_bio_source: LinkedIn
1717
sidebar:
1818
order: 2
19-
2019
---
2120

2221
import { Render, TabItem, Tabs } from "~/components";
2322

24-
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
25-
23+
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.
2624

2725
## Before you begin
2826

29-
- You must have a Cloudflare account
30-
- You must have a Stripe account
27+
<Render file="prereqs" product="workers" />
28+
3. Sign up for a [Stripe](https://stripe.com) account.
3129

30+
## 1. Get Your Turnstile sitekey and secret key
3231

33-
## Get Your Turnstile sitekey and secret key
32+
First, you will need to prepare a Cloudflare Turnstile widget to use for this application.
3433

3534
1. Log in to the [Cloudflare dashboard](https://dash.cloudflare.com/) and select your account.
3635
2. Go to **Turnstile** and [create a new Turnstile widget](/turnstile/get-started/).
3736
3. Copy the sitekey and the secret key to use in the next step.
3837

39-
## 1. Create a new Worker project
38+
## 2. Create a new Worker project
4039

41-
42-
First, let's create a Cloudflare Workers project.
40+
Now that your Turnstile widget it ready to use, you can create your Worker application.
4341

4442
<Render file="c3-definition" product="workers" />
4543

4644
To efficiently create and manage multiple APIs, let's use [`Hono`](https://hono.dev). Hono is an open-source application framework released by a Cloudflare Developer Advocate. It is lightweight and allows for the creation of multiple API paths, as well as efficient request and response handling.
45+
4746
Open your command line interface (CLI) and run the following command:
4847

4948
<Tabs> <TabItem label="npm">
@@ -82,7 +81,6 @@ [email protected]
8281
Ok to proceed? (y) y
8382
```
8483

85-
8684
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.
8785

8886
```sh
@@ -150,21 +148,19 @@ curl http://localhost:8787
150148
Hello Hono!
151149
```
152150

153-
So far, we've covered how to create a Cloudflare Worker project and introduced tools and open-source projects like the `C3` command and the `Hono` framework that streamline development with Cloudflare. Leveraging these features will help you develop applications on Cloudflare Workers more smoothly.
154-
155-
## 2. Preparation for the building web application
151+
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.
156152

157-
By the default, we need to update the Hono project for supporting to web application.
153+
At the next step, we need to update the Hono project for supporting to web application.
158154

159-
### Change the file extension to support JSX
155+
## 3. Change index script file extension to .JSX
160156

161-
Since we'll use JSX to dynamically create HTML, let's change `src/index.ts` to `src/index.tsx`.
157+
Since we will use JSX to dynamically create HTML, you will need to change `src/index.ts` to `src/index.tsx`.
162158

163159
```
164160
mv src/index.ts src/index.tsx
165161
```
166162

167-
At the same time, let's change the filename specified in the `wrangler.toml`.
163+
At the same time, change the filename specified in the `wrangler.toml`.
168164

169165
```diff
170166
#:schema node_modules/wrangler/config-schema.json
@@ -174,19 +170,15 @@ name = "secure-payment-form"
174170
compatibility_date = "2024-10-22"
175171
```
176172

177-
### Register Stripe API key and Turnstile key as environment variables
178-
179-
Let's register API keys as environment variables to use both Stripe and Cloudflare Turnstile.
173+
## 4. Add the Stripe API key and Turnstile key as environment variables
180174

181-
You can obtain test site keys and secret keys for Cloudflare Turnstile from the documentation.
175+
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.
182176

183-
https://developers.cloudflare.com/turnstile/reference/testing/
177+
You can obtain test site keys and secret keys for Turnstile from the [Turnstile documentation](/turnstile/reference/testing/).
184178

185-
Get the publishable key and secret key for Stripe from the Stripe dashboard.
179+
Get the publishable key and secret key for Stripe from the [Stripe dashboard].(https://dashboard.stripe.com/test/apikeys)
186180

187-
https://dashboard.stripe.com/test/apikeys
188-
189-
And place each keys into the `.dev.vars` file like the following:
181+
Then, place each key into the `.dev.vars` file like the following:
190182

191183
```
192184
TURNSTILE_SITE_KEY = '1x00000000000000000000AA'
@@ -196,6 +188,7 @@ STRIPE_SECRET_KEY='Secret key starting with sk_test_'
196188
```
197189

198190
After that, you can generate TypeScript type definition by running the `npm run cf-typegen` command.
191+
199192
```sh
200193
$ npm run cf-typegen
201194

@@ -212,17 +205,17 @@ interface CloudflareBindings {
212205
In local development using Hono and Wrangler, you can retrieve values set in `.dev.vars` like this:
213206

214207
```ts
215-
app.get('/hello', async c => {
216-
console.log(c.env.TURNSTILE_SITE_KEY);
217-
return c.json({ message: 'test' });
218-
})
208+
app.get("/hello", async (c) => {
209+
console.log(c.env.TURNSTILE_SITE_KEY);
210+
return c.json({ message: "test" });
211+
});
219212
```
220213

221-
Now we're ready for application development. In the next steps, we'll develop a payment form using Turnstile and Stripe.
214+
Now we are ready for application development. In the next steps, we will develop a payment form using Turnstile and Stripe.
222215

223-
## 3. Implementing Bot Detection with Turnstile
216+
## 5. Implement Bot Detection with Turnstile
224217

225-
Let's 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:
218+
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:
226219

227220
```ts
228221
import { Hono } from "hono";
@@ -247,8 +240,8 @@ export default app;
247240

248241
```
249242

250-
Let's add JavaScript code to our application to implement bot detection using Turnstile.
251-
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 is not from a bot.
243+
Add JavaScript code to our application to implement bot detection using Turnstile.
244+
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.
252245

253246
```diff
254247
import { Hono } from "hono";
@@ -297,15 +290,15 @@ export default app;
297290

298291
```
299292

300-
Here, we're loading 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.
293+
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.
301294

302295
In the `_turnstileCB` function, `turnstile.render()` is executed. The `callback` set here removes the `disabled` attribute from the submit `button` of the `form`.
303296

304-
This simple implementation prevents order operations for accesses that Cloudflare identifies as bots.
297+
This implementation blocks order operations for any requests that Cloudflare identifies as being made by a bots.
305298

306-
## 4. Integrating Turnstile with a Stripe Payment Form
299+
## 6. Integrate Turnstile with a Stripe payment form
307300

308-
For a more practical example, let's integrate Turnstile with a Stripe payment form. First, install the Stripe SDK:
301+
To integrate Turnstile with a Stripe payment form, first you will need to install the Stripe SDK:
309302

310303
<Tabs> <TabItem label="npm">
311304

@@ -327,8 +320,7 @@ pnpm add stripe
327320

328321
</TabItem> </Tabs>
329322

330-
Next, let's implement the code to create a payment form in `src/index.tsx`.
331-
First, create a [Payment Intent](https://docs.stripe.com/api/payment_intents) on the server side:
323+
Next, implement the code to create a payment form in `src/index.tsx`. The following code creates a [Payment Intent](https://docs.stripe.com/api/payment_intents) on the server side:
332324

333325
```diff
334326
import { Hono } from "hono";
@@ -358,7 +350,7 @@ app.get("/", async (c) => {
358350

359351
```
360352

361-
And adding JavaScript code to display the payment form. Edit `src/index.tsx`:
353+
Then, add the following code to display the payment form. Edit `src/index.tsx`:
362354

363355
```diff
364356
{html`
@@ -431,98 +423,102 @@ The payment form is now ready. To experience how it behaves when a bot is detect
431423
+TURNSTILE_SITE_KEY = '2x00000000000000000000AB'
432424
```
433425

434-
If you restart the application now, you'll notice that you can't submit the payment form.
426+
If you restart the application now, you will notice that you cannot submit the payment form.
435427

436428
![Failed challenge](~/assets/images/turnstile/payment-form.png)
437429

438-
This way, you can block requests trying to manipulate the payment form using bots, such as card testing attacks.
430+
This way, you can block requests that use bots to try and manipulate the payment form, such as card testing attacks.
439431
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.
440432

441-
Note: After completing the tests, let's make sure to revert the Turnstile SITE_KEY back to its original value.
433+
:::note
434+
After completing the tests, make sure to revert the Turnstile `SITE_KEY` back to its original value.
435+
442436
```diff
443437
+TURNSTILE_SITE_KEY = '1x00000000000000000000AA'
444438
-TURNSTILE_SITE_KEY = '2x00000000000000000000AB'
445439
```
446440

447-
## 5. Adding Server-Side Validation
441+
:::
442+
443+
## 7. Add Server-Side Validation
448444

449-
Let's add a step to verify that the token generated by the Turnstile widget is valid and not forged.
445+
Next, add a step to verify that the token generated by the Turnstile widget is valid and not forged.
450446
In this case, we'll add an API that performs additional validation and server-side processing based on the result of `turnstile.render`.
451447

452-
First, for easier testing, let's remove the `disabled` attribute from the `button` tag:
448+
For easier testing, remove the `disabled` attribute from the `button` tag:
453449

454450
```diff
455451
- <button type="submit" disabled>Order</button>
456452
+ <button type="submit" >Order</button>
457453
```
458454

459-
Next, let's add an API for server-side verification. Please add the following code to `src/index.tsx`.
455+
Next, add an API for server-side verification. Please add the following code to `src/index.tsx`.
460456
This API validates the Turnstile token generated by the client application and incorporates the result into Stripe's Payment Intent.
461457

462458
```ts
463459
import { HTTPException } from "hono/http-exception";
464460

465461
type TurnstileResult = {
466-
success: boolean;
467-
challenge_ts: string;
468-
hostname: string;
469-
"error-codes": Array<string>;
470-
action: string;
471-
cdata: string;
462+
success: boolean;
463+
challenge_ts: string;
464+
hostname: string;
465+
"error-codes": Array<string>;
466+
action: string;
467+
cdata: string;
472468
};
473469

474470
app.post("/pre-confirm", async (c) => {
475-
const { TURNSTILE_SECRET_KEY, STRIPE_SECRET_KEY } = env(c);
476-
const stripe = new Stripe(STRIPE_SECRET_KEY, {
477-
apiVersion: "2024-10-28.acacia",
478-
appInfo: {
479-
name: "example/cloudflare-turnstile",
480-
},
481-
});
482-
483-
const body = await c.req.json();
484-
const ip = c.req.header("CF-Connecting-IP");
485-
const paymentIntentId = body.payment_intent_id
486-
487-
const formData = new FormData();
488-
formData.append("secret", TURNSTILE_SECRET_KEY);
489-
formData.append("response", body.turnstile_token);
490-
formData.append("remoteip", ip || "");
491-
const turnstileResult = await fetch(
492-
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
493-
{
494-
body: formData,
495-
method: "POST",
496-
},
497-
);
498-
const outcome = await turnstileResult.json<TurnstileResult>();
499-
500-
await stripe.paymentIntents.update(paymentIntentId, {
501-
metadata: {
502-
turnstile_result: outcome.success ? 'success' : 'failed',
503-
turnstile_challenge_ts: outcome.challenge_ts,
504-
},
505-
})
506-
507-
if (!outcome.success) {
508-
throw new HTTPException(401, {
509-
res: new Response(
510-
JSON.stringify({
511-
success: outcome.success,
512-
message: "Unauthorized",
513-
error_codes: outcome["error-codes"],
514-
}),
515-
),
516-
});
517-
}
518-
return c.json({
519-
success: outcome.success
520-
});
471+
const { TURNSTILE_SECRET_KEY, STRIPE_SECRET_KEY } = env(c);
472+
const stripe = new Stripe(STRIPE_SECRET_KEY, {
473+
apiVersion: "2024-10-28.acacia",
474+
appInfo: {
475+
name: "example/cloudflare-turnstile",
476+
},
477+
});
478+
479+
const body = await c.req.json();
480+
const ip = c.req.header("CF-Connecting-IP");
481+
const paymentIntentId = body.payment_intent_id;
482+
483+
const formData = new FormData();
484+
formData.append("secret", TURNSTILE_SECRET_KEY);
485+
formData.append("response", body.turnstile_token);
486+
formData.append("remoteip", ip || "");
487+
const turnstileResult = await fetch(
488+
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
489+
{
490+
body: formData,
491+
method: "POST",
492+
},
493+
);
494+
const outcome = await turnstileResult.json<TurnstileResult>();
495+
496+
await stripe.paymentIntents.update(paymentIntentId, {
497+
metadata: {
498+
turnstile_result: outcome.success ? "success" : "failed",
499+
turnstile_challenge_ts: outcome.challenge_ts,
500+
},
501+
});
502+
503+
if (!outcome.success) {
504+
throw new HTTPException(401, {
505+
res: new Response(
506+
JSON.stringify({
507+
success: outcome.success,
508+
message: "Unauthorized",
509+
error_codes: outcome["error-codes"],
510+
}),
511+
),
512+
});
513+
}
514+
return c.json({
515+
success: outcome.success,
516+
});
521517
});
522-
523518
```
524519

525520
Then, add the process to call the created API.
521+
526522
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:
527523

528524
```diff
@@ -572,11 +568,13 @@ paymentForm.addEventListener("submit", async (e) => {
572568
```
573569

574570
By adding this step, we now perform a two-stage check using Turnstile before the payment process.
575-
Since we're saving the Turnstile authentication result in the Stripe data, it's also easier to investigate if a user reports a payment failure.
571+
572+
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.
576573

577574
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.
578575

579576
## Conclusion
580577

581-
In online payments, it's necessary to protect applications from bot attacks such as card testing and DDoS.
582-
While payment services like Stripe are increasingly implementing bot prevention measures, adding Turnstile can provide an extra layer of security for your payment forms.
578+
In online payments, it ss necessary to protect applications from bot attacks such as card testing and DDoS attacks.
579+
580+
While payment services like Stripe are increasingly implementing bot prevention measures, adding Turnstile can provide an extra layer of security for your payment forms.

0 commit comments

Comments
 (0)