Skip to content

Commit

Permalink
add Cloudflare Turnstile example (#41283)
Browse files Browse the repository at this point in the history
## Description
close #41110 

## Documentation / Examples

- [x] Make sure the linting passes by running `pnpm lint`
- [x] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
maxam2017 committed Oct 12, 2022
1 parent 6652d78 commit 9b106db
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 0 deletions.
5 changes: 5 additions & 0 deletions examples/cloudflare-turnstile/.env.local.example
@@ -0,0 +1,5 @@
# Public Environment variables that can be used in the browser.
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=

# Secret environment variables only available to Node.js
CLOUDFLARE_TURNSTILE_SECRET_KEY=
36 changes: 36 additions & 0 deletions examples/cloudflare-turnstile/.gitignore
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
55 changes: 55 additions & 0 deletions examples/cloudflare-turnstile/README.md
@@ -0,0 +1,55 @@
# Example with Cloudflare Turnstile

[Turnstile](https://developers.cloudflare.com/turnstile/) is Cloudflare’s smart CAPTCHA alternative. It can be embedded into any website without sending traffic through Cloudflare and works without showing visitors a CAPTCHA.

This example shows how you can use **Cloudflare Turnstile** with your Next.js project. You can see a [live version here](https://with-cloudflare-turnstile.vercel.app/).

## Deploy your own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example).

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cloudflare-turnstile&project-name=cloudflare-turnstile&repository-name=cloudflare-turnstile)

**Important**: When you import your project on Vercel, make sure to click on **Environment Variable**s and set them to match your .env.local file.

## How to use

Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:

```bash
npx create-next-app --example cloudflare-turnstile cloudflare-turnstile-app
```

```bash
yarn create next-app --example cloudflare-turnstile cloudflare-turnstile-app
```

```bash
pnpm create next-app --example cloudflare-turnstile cloudflare-turnstile-app
```

Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

## Configuring Cloudflare Turnstile

### Get a sitekey and secret key

1. Go to the [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/turnstile) and select your account.
2. Go to Turnstile.
3. Select Add a site and fill out the form.
4. Copy your **Site Key** and **Secret Key**.

### Set up environment variables

To connect the app with Cloudflare Turnstile, you'll need to add the settings from your Cloudflare dashboard as environment variables

Copy the .env.local.example file in this directory to .env.local.

```bash
cp .env.local.example .env.local
```

Then, open .env.local and fill these environment variables:

- `NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY`
- `CLOUDFLARE_TURNSTILE_SECRET_KEY`
30 changes: 30 additions & 0 deletions examples/cloudflare-turnstile/app.css
@@ -0,0 +1,30 @@
html,
body {
display: grid;
place-content: center;
margin: 0;
height: 100%;
}

main {
height: 50vh;
width: 300px;
text-align: center;
}

button[type='submit'] {
cursor: pointer;
width: 100%;
outline: none;
border: none;
padding: 0.5rem 1rem;
margin-top: 1rem;
border-radius: 0.25rem;
background: #0d6efd;
font-size: 1.25rem;
color: #ffffff;
}

.checkbox {
min-height: 70px;
}
10 changes: 10 additions & 0 deletions examples/cloudflare-turnstile/next.config.js
@@ -0,0 +1,10 @@
module.exports = {
async rewrites() {
return [
{
source: '/',
destination: '/implicit',
},
]
},
}
19 changes: 19 additions & 0 deletions examples/cloudflare-turnstile/package.json
@@ -0,0 +1,19 @@
{
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^18.0.0",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.5",
"typescript": "^4.7.4"
}
}
6 changes: 6 additions & 0 deletions examples/cloudflare-turnstile/pages/_app.tsx
@@ -0,0 +1,6 @@
import type { AppProps } from 'next/app'
import '../app.css'

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
18 changes: 18 additions & 0 deletions examples/cloudflare-turnstile/pages/api/handler.ts
@@ -0,0 +1,18 @@
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function Handler(
req: NextApiRequest,
res: NextApiResponse
) {
const form = new URLSearchParams()
form.append('secret', process.env.CLOUDFLARE_TURNSTILE_SECRET_KEY)
form.append('response', req.body['cf-turnstile-response'])
form.append('remoteip', req.headers['x-forwarded-for'] as string)

const result = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{ method: 'POST', body: form }
)
const json = await result.json()
res.status(result.status).json(json)
}
43 changes: 43 additions & 0 deletions examples/cloudflare-turnstile/pages/explicit.tsx
@@ -0,0 +1,43 @@
import Script from 'next/script'

type RenderParameters = {
sitekey: string
theme?: 'light' | 'dark'
callback?(token: string): void
}

declare global {
interface Window {
onloadTurnstileCallback(): void
turnstile: {
render(container: string | HTMLElement, params: RenderParameters): void
}
}
}

export default function ExplicitRender() {
return (
<main>
<Script id="cf-turnstile-callback">
{`window.onloadTurnstileCallback = function () {
window.turnstile.render('#my-widget', {
sitekey: '${process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY}',
})
}`}
</Script>
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback"
async={true}
defer={true}
/>
<form method="POST" action="/api/handler">
<h2>Dummy Login Demo</h2>
<div id="my-widget" className="checkbox" />
<button type="submit">Sign in</button>
<p>
Go to the <a href="/implicit">implicit render demo</a>
</p>
</form>
</main>
)
}
24 changes: 24 additions & 0 deletions examples/cloudflare-turnstile/pages/implicit.tsx
@@ -0,0 +1,24 @@
import Script from 'next/script'

export default function ImplicitRender() {
return (
<main>
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async={true}
defer={true}
/>
<form method="POST" action="/api/handler">
<h2>Dummy Login Demo</h2>
<div
className="cf-turnstile checkbox"
data-sitekey={process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY}
/>
<button type="submit">Sign in</button>
<p>
Go to the <a href="/explicit">explicit render demo</a>
</p>
</form>
</main>
)
}
20 changes: 20 additions & 0 deletions examples/cloudflare-turnstile/tsconfig.json
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

0 comments on commit 9b106db

Please sign in to comment.