Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Cloudflare Turnstile example #41283

Merged
merged 10 commits into from Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"]
}