Cloudflare Turnstile integration for React.

Overview

React Turnstile

Cloudflare Turnstile integration for React.


npm version npm downloads install size bundle size CI status tests missing PRs are welcome

Features

  • 💪 smart verification with minimal user interaction
  • 🕵️‍♀️ privacy-focused approach
  • 💉 automatic script injection
  • ⚡️ ssr ready

Demo

https://react-turnstile.vercel.app/

Install

  1. Follow these steps to obtain a free site key and secret key from Cloudflare.

  2. Install @marsidev/react-turnstile into your React application.

    # Whichever matches your package manager
    pnpm add @marsidev/react-turnstile
    npm install @marsidev/react-turnstile
    yarn add @marsidev/react-turnstile
    ultra install @marsidev/react-turnstile

Usage

The only required prop is the siteKey.

import { Turnstile } from '@marsidev/react-turnstile'

function Widget() {
  return <Turnstile siteKey='1x00000000000000000000AA' />
}

Props

Prop Type Description Required
siteKey string Your sitekey key, get one from here.
options object Widget render options. More info about this options below.
scriptProps object You can customize the injected script tag with this prop. It allows you to add async, defer, nonce attributes to the script tag. You can also control whether the injected script will be added to the document body or head with appendTo attribute.
onSuccess function Callback that is invoked upon success of the challenge. The callback is passed a token that can be validated.
onExpire function Callback that is invoked when a challenge expires.
onError function Callback that is invoked when there is a network error.

Render options

Option Type Default Description
theme string 'auto' The widget theme. You can choose between light, dark or auto.
tabIndex number 0 The tabindex of Turnstile’s iframe for accessibility purposes.
action string undefined A customer value that can be used to differentiate widgets under the same sitekey in analytics and which is returned upon validation. This can only contain up to 32 alphanumeric characters including _ and -.
cData string undefined A customer payload that can be used to attach customer data to the challenge throughout its issuance and which is returned upon validation. This can only contain up to 255 alphanumeric characters including _ and -.
responseField boolean true A boolean that controls if an input element with the response token is created.
responseFieldName string 'cf-turnstile-response' Name of the input element.
size string 'normal' The widget size. Can take the following values: 'normal', 'compact'. The normal size is 300x65px, the compact size is 130x120px.

All this options are optional.

Read the docs to get more info about this options.

The widget is wrapped in a div, so you can pass any valid div prop such as className, id, or style.

Script options

Option Type Default Description
nonce string undefined Custom nonce for the injected script.
defer boolean true Define if set the injected script as defer.
async boolean true Define if set the injected script as async.
appendTo string 'head' Define if inject the script in the head or in the body.
id string 'cf-turnstile-script' Custom ID of the injected script.
onLoadCallbackName string 'onloadTurnstileCallback' Custom name of the onload callback.

Examples

Rendering the widget:

import { Turnstile } from '@marsidev/react-turnstile'

function Widget() {
  return <Turnstile siteKey='1x00000000000000000000AA' />
}

Rendering the widget with custom props:

import { Turnstile } from '@marsidev/react-turnstile'

function Widget() {
  return (
    <Turnstile
      siteKey='1x00000000000000000000AA'
      className='fixed bottom-4 right-4'
      options={{
        action: 'submit-form',
        theme: 'light',
        size: 'compact'
      }}
      scriptOptions={{
        appendTo: 'body'
      }}
    />
  )
}

Managing widget rendering status:

import { useState } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'

function Widget() {
  const [status, setStatus] = useState()

  return (
    <Turnstile
      siteKey='1x00000000000000000000AA'
      onError={() => setStatus('error')}
      onExpire={() => setStatus('expired')}
      onSuccess={() => setStatus('solved')}
    />
  )
}

Getting the token after solving the challenge:

import { useState } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'

function Widget() {
  const [token, setToken] = useState()

  return (
    <Turnstile
      siteKey='1x00000000000000000000AA'
      onSuccess={(token) => setToken(token)}
    />
  )
}

Interacting with the widget:

import { useRef } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'

function Widget() {
  const ref = useRef(null)

  return (
    <>
      <Turnstile ref={ref} siteKey='1x00000000000000000000AA'/>

      <button onClick={() => alert(ref.current?.getResponse())}>
        Get response
      </button>

      <button onClick={() => ref.current?.reset()}>
        Reset widget
      </button>

      <button onClick={() => ref.current?.remove()}>
        Remove widget
      </button>

      <button onClick={() => ref.current?.render()}>
        Render widget
      </button>
    </>
  )
}

Interacting with the widget (using TypeScript):

import { useRef } from 'react'
import { Turnstile, type TurnstileInstance } from '@marsidev/react-turnstile'

function Widget() {
  const ref = useRef<TurnstileInstance>(null)

  return (
    <>
      <Turnstile ref={ref} siteKey='1x00000000000000000000AA'/>

      <button onClick={() => alert(ref.current?.getResponse())}>
        Get response
      </button>
    </>
  )
}

Validating a token:

// LoginForm.jsx
import { useRef, useState } from 'react'
import { Turnstile } from '@marsidev/react-turnstile'

export default function LoginForm() {
  const formRef = useRef(null)

  async function handleSubmit(event) {
    event.preventDefault()
    const formData = new FormData(formRef.current)
    const token = formData.get('cf-turnstile-response')

    const res = await fetch('/api/verify', {
      method: 'POST',
      body: JSON.stringify({ token }),
      headers: {
        'content-type': 'application/json'
      }
    })

    const data = await res.json()
    if (data.success) {
      // the token has been validated
    }
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" placeholder="username"/>
      <input type="password" placeholder="password"/>
      <Turnstile siteKey='1x00000000000000000000AA'/>
      <button type='submit'>Login</button>
    </form>
  )
}
// `pages/api/verify.js`
// this is an example of a next.js api route
// this code runs on the server
const endpoint = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
const secret = '1x0000000000000000000000000000000AA'

export default async function handler(request, response) {
  const body = `secret=${encodeURIComponent(secret)}&response=${encodeURIComponent(request.body.token)}`

  const res = await fetch(endpoint, {
    method: 'POST',
    body,
    headers: {
      'content-type': 'application/x-www-form-urlencoded'
    }
  })

  const data = await res.json()
  return response.json(data)
}

Check the demo and his source code to see a code similar to the above in action.

Check the docs for more info about server side validation.

As you might noted, there is three ways to get the token response from a solved challenge:

  • by catching it from the onSuccess callback.
  • by calling the .getResponse() method.
  • by reading the widget response input with name cf-turnstile-response. This one is not an option if you set options.fieldResponse to false.

Contributing

Any contributions are greatly appreciated. If you have a suggestion that would make this project better, please fork the repo and create a Pull Request. You can also open an issue.

Development

  • Fork or clone this repository.
  • Install pnpm.
  • Install dependencies with pnpm install.
  • You can use pnpm dev to start the demo page in development mode, which also rebuild the library when file changes are detected in the src folder.
  • You also can use pnpm stub, which run unbuild --stub, a passive watcher to use the library while developing without needing to watch and rebuild. However, this option can't be used in an esm context.

Credits

Inspired by

License

Published under the MIT License.

You might also like...
React-Login-app - Create a Login and Registration Page with React Router
React-Login-app - Create a Login and Registration Page with React Router

About In this repo I was given a Task to create a Login and Registration page wh

Insomnia-react-app - A react app that will help you relax and sleep better
Insomnia-react-app - A react app that will help you relax and sleep better

insomnia - a sleep inducer app An app that will help you relax and sleep better.

React-three-ts-bp - [TS] Stable version for React-Three + Spring & Tailwind bp

React-three-ts-bp - [TS] Stable version for React-Three + Spring & Tailwind bp

Aviasales React-Redux-MobX- - A quick start Redux + TypeScript Create React App template

A quick start Redux + TypeScript Create React App template An opinionated quick

A simple way to write re-usable features with React + EffectorA simple way to write re-usable features with React + Effector

Effector Factorio The simplest way to write re-usable features with React + Effector Install npm install effector-factorio Why this? People became to

Calculator-in-react - Calculator made in React.JS using Hooks as useReducer
Calculator-in-react - Calculator made in React.JS using Hooks as useReducer

Calculator-in-react - Calculator made in React.JS using Hooks as useReducer

Thirdweb react SDK: The official React.JS wrapper for the thirdweb sdk.

The thirdweb React SDK provides a collection of hooks to use in your React apps to interact with your thirdweb contracts.

React-fade: Proof of Concept react fade in/out that just works without any effort
React-fade: Proof of Concept react fade in/out that just works without any effort

react-fade Proof of Concept react fade in/out that just works without any effort. Inspired by react native layout animations. How it works FadeIn noth

React Query Typed Api - An opinioneted wrapper around react-query to implement fully typed api requests
React Query Typed Api - An opinioneted wrapper around react-query to implement fully typed api requests

React Query Typed Api - An opinioneted wrapper around react-query to implement fully typed api requests

Comments
  • Cannot import module, unexpected token

    Cannot import module, unexpected token

    When I try to import the package, I get an error:

    ./node_modules/@marsidev/react-turnstile/dist/index.mjs
    Module parse failed: Unexpected token (12:51)
    You may need an appropriate loader to handle this file type.
    |   onLoadCallbackName,
    |   onLoad,
    |   scriptOptions: { nonce = "", defer = true, async = true, id = "", appendTo } = {}
    | }) => {
    |   const scriptId = id || DEFAULT_SCRIPT_ID;
    

    Line 12 is

      scriptOptions: { nonce = "", defer = true, async = true, id = "", appendTo } = {}
    

    The issue appears to be that it's using async as a parameter name.

    opened by advaith1 3
  • What is the proper way to handle multiple submits with captcha approach?

    What is the proper way to handle multiple submits with captcha approach?

    Hey! First I wanted to thank you for the amazing library, it works like a charm. I was wondering what is the proper way to handle a secured captcha submit form when the user can click more than once but with different parameters. It feels a bit weird to reset the widget to generate a new token to be submitted if I already know that the current user is legit. One solution would be to redirect to a new page with the result of the form but it would be a pain for my user case. Maybe a little cache in the vercel function with the user ip would do the trick? Thanks in advance!

    opened by angelhodar 5
  • Partytown integration

    Partytown integration

    Hi, would it be possible to allow Partytown integration? Either via support for completely custom scripts (so that we can use Next.js script tag) or some other way? :)

    opened by pegak 2
Releases(v0.0.5)
Owner
Luis Marsiglia
Full Stack Developer and Data Analyst
Luis Marsiglia
Very quick and easy integration of the Potree Viewer in React

Very quick and easy integration of the Potree Viewer in React

null 7 Nov 7, 2022
:tada: React Universal Hooks : just use****** everywhere (Functional or Class Component). Support React DevTools!

react-universal-hooks React Universal Hooks : just use****** everywhere. Support React >= 16.8.0 Installation Using npm: $ npm install --save react-un

Salvatore Ravidà 177 Oct 10, 2022
React ESI: Blazing-fast Server-Side Rendering for React and Next.js

React ESI: Blazing-fast Server-Side Rendering for React and Next.js React ESI is a super powerful cache library for vanilla React and Next.js applicat

Kévin Dunglas 632 Dec 29, 2022
30 Days of React challenge is a step by step guide to learn React in 30 days

30 Days of React challenge is a step by step guide to learn React in 30 days. It requires HTML, CSS, and JavaScript knowledge. You should be comfortable with JavaScript before you start to React. If you are not comfortable with JavaScript check out 30DaysOfJavaScript. This is a continuation of 30 Days Of JS. This challenge may take up to 100 days, follow your own pace.

Asabeneh 18.4k Jan 5, 2023
React Navigation for React js that tries to ensure that the elements of the navigators display correctly on devices with notches and UI elements which may overlap the app content.

React Navigation for React js that tries to ensure that the elements of the navigators display correctly on devices with notches (e.g. iPhone X) and UI elements which may overlap the app content.

MohammadAli Karimi 11 Nov 27, 2022
React People lists and connects React developers around the world.

React People React People lists and connects React developers around the world. Progress View the Project page for details on what's planned and what'

Vitor Leonel 15 Aug 23, 2022
React Backbone Binding that works with React 16

WithBackbone React Higher Order Component which will bind Backbone Data that works with React Fiber (v.16) Why did we make it There are already a coup

Beanworks 2 Mar 19, 2021
A description of React's new core algorithm, React Fiber

React Fiber Architecture Introduction React Fiber is an ongoing reimplementation of React's core algorithm. It is the culmination of over two years of

Andrew Clark 9.8k Jan 5, 2023
React Clone - How React Works: An In-Depth Guide

Как работает React: подробное руководство Привет, друзья! В этой статье я покажу вам, с чего начинается React. Что это означает? Это означает, что мы

Igor Agapov 5 Nov 25, 2021
A drum machine built with React using the Create React App toolchain.

drum-machine A simple drum machine built with React using the Create React App toolchain. Getting Started Clone or download the repository. Open a com

Ryan Schafer 1 Dec 30, 2021