ComCool
ComCool

Reputation: 1103

React and reCAPTCHA v3

Is there any easy way to use reCAPTCHA v3 in react? Did a google search an can only find components for v2. And only react-recaptcha-v3 for v3.

But I get an error Invalid site key or not loaded in api.js when I try to use the component.

Upvotes: 17

Views: 41518

Answers (5)

Alan
Alan

Reputation: 10145

You can also create your own custom hook useReCaptcha with React (Typescript):

// hooks/useReCaptcha.ts

import { RECAPTCHA_KEY, RECAPTCHA_TOKEN } from 'config/config'
import { useEffect, useState } from 'react'

const showBadge = () => {
  if (!window.grecaptcha) return
  window.grecaptcha.ready(() => {
    const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
    if (!badge) return
    badge.style.display = 'block'
    badge.style.zIndex = '1'
  })
}

const hideBadge = () => {
  if (!window.grecaptcha) return
  window.grecaptcha.ready(() => {
    const badge = document.getElementsByClassName('grecaptcha-badge')[0] as HTMLElement
    if (!badge) return
    badge.style.display = 'none'
  })
}

const useReCaptcha = (): { reCaptchaLoaded: boolean; generateReCaptchaToken: (action: string) => Promise<string> } => {
  const [reCaptchaLoaded, setReCaptchaLoaded] = useState(false)

  // Load ReCaptcha script
  useEffect(() => {
    if (typeof window === 'undefined' || reCaptchaLoaded) return
    if (window.grecaptcha) {
      showBadge()
      setReCaptchaLoaded(true)
      return
    }
    const script = document.createElement('script')
    script.async = true
    script.src = `https://www.google.com/recaptcha/api.js?render=${RECAPTCHA_KEY}`
    script.addEventListener('load', () => {
      setReCaptchaLoaded(true)
      showBadge()
    })
    document.body.appendChild(script)
  }, [reCaptchaLoaded])

  // Hide badge when unmount
  useEffect(() => hideBadge, [])

  // Get token
  const generateReCaptchaToken = (action: string): Promise<string> => {
    return new Promise((resolve, reject) => {
      if (!reCaptchaLoaded) return reject(new Error('ReCaptcha not loaded'))
      if (typeof window === 'undefined' || !window.grecaptcha) {
        setReCaptchaLoaded(false)
        return reject(new Error('ReCaptcha not loaded'))
      }
      window.grecaptcha.ready(() => {
        window.grecaptcha.execute(RECAPTCHA_KEY, { action }).then((token: string) => {
          localStorage.setItem(RECAPTCHA_TOKEN, token)
          resolve(token)
        })
      })
    })
  }

  return { reCaptchaLoaded, generateReCaptchaToken }
}

export default useReCaptcha

Then in the login component for example, you can call this custom hook:

// Login.ts

import React from 'react'
import useReCaptcha from 'hooks/useReCaptcha'

const LoginPageEmail = () => {
  const { reCaptchaLoaded, generateReCaptchaToken } = useReCaptcha()

  const login = async () => {
    await generateReCaptchaToken('login') // this will create a new token in the localStorage
    await callBackendToLogin() // get the token from the localStorage and pass this token to the backend (in the cookies or headers or parameter..)
  }

  return (
    <button disabled={!reCaptchaLoaded} onClick={login}>
      Login
    </button>
  )
}

export default LoginPageEmail

Upvotes: 5

Armen Stepanyan
Armen Stepanyan

Reputation: 1846

You can use react-google-recaptcha3 npm package (size: ~5 KB)

npm i react-google-recaptcha3

Usage

import ReactRecaptcha3 from 'react-google-recaptcha3';

const YOUR_SITE_KEY = '';

function App() {
 // load google recaptcha3 script
  useEffect(() => {
    ReactRecaptcha3.init(YOUR_SITE_KEY).then(
      (status) => {
        console.log(status);
      }
    );
  }, [])

}

Now on form submit you need to generate token and then append it to your form data

  const submit = () => {
    const formData = { name: "John", lastname: "Doe" }
    ReactRecaptcha3.getToken().then(
      (token) => {
        console.log(token);
        formData.token = token;
        // send request to backend
        fetch(url, { method: 'POST', body: JSON.stringify(formData) }).then(...)
        
      },
      (error) => {
        console.log(error);
      }
    );
  };

Now in backend you need to validate token

const request = require('request-promise');

const secretKey = YOUR_RECAPTCHA_SECRET_KEY;
const userIp = 'USER_IP';
request.get({
    url: `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptchaToken}&remoteip=${userIp}`,
}).then((response) => {

    // If response false return error message
    if (response.success === false) {
        return res.json({
            success: false,
            error: 'Recaptcha token validation failed'
        });
    }
    // otherwise continue handling/saving form data
    next();
})

Stackblitz example

Upvotes: 4

HomeIsWhereThePcIs
HomeIsWhereThePcIs

Reputation: 1464

I am teaching myself React + TypeScript and this is what I came up with to implement recaptcha v3.

I wanted a simple solution that would allow me to:

  • get the token dynamically only when the form is submitted to avoid timeouts and duplicate token errors
  • use recaptcha only on some components for privacy reasons (eg. login, register, forgot-password) instead of globally defining recaptcha api.js in index.html
  • require the least code possible to implement in a component

reCAPTCHA.ts

declare global {
    interface Window {
        grecaptcha: any;
    }
}

export default class reCAPTCHA {
    siteKey: string;
    action: string;

    constructor(siteKey: string, action: string) {
        loadReCaptcha(siteKey);
        this.siteKey = siteKey;
        this.action = action;
    }

    async getToken(): Promise<string> {
        let token = "";
        await window.grecaptcha.execute(this.siteKey, {action: this.action})
            .then((res: string) => {
                token = res;
            })
        return token;
    }
}

const loadReCaptcha = (siteKey: string) => {
    const script = document.createElement('script')
    script.src = `https://www.recaptcha.net/recaptcha/api.js?render=${siteKey}`
    document.body.appendChild(script)
}

To use this class declare it as a property in the component:

recaptcha = new reCAPTCHA((process.env.REACT_APP_RECAPTCHA_SITE_KEY!), "login");

And on form submit get the token that you need to pass to backend:

let token: string = await this.recaptcha.getToken();

To verify the token on the backend:

recaptcha.ts

const fetch = require("node-fetch");
const threshold = 0.6;

export async function validateRecaptcha(recaptchaToken: string, expectedAction: string) : Promise<boolean> {
    const recaptchaSecret = process.env.RECAPTCHA_SECRET_KEY;
    const url = `https://www.recaptcha.net/recaptcha/api/siteverify?secret=${recaptchaSecret}&response=${recaptchaToken}`;
    let valid = false;
    await fetch(url, {method: 'post'})
        .then((response: { json: () => any; }) => response.json())
        .then((data: any)=> {
            valid = (data.success && data.score && data.action && data.score >= threshold && data.action === expectedAction);
        });
    return valid;
}

I have very limited experience with JS/TS and React but this solution does work for me. I welcome any input on improving this code.

Upvotes: 7

Alex Dunlop
Alex Dunlop

Reputation: 1606

Hey you don't need a package, its just an unnecessary package you don't need. https://medium.com/@alexjamesdunlop/unnecessary-packages-b3623219d86 I wrote an article about why you shouldn't use it and another package. Don't rely on some package! Rely on google instead :)

const handleLoaded = _ => {
  window.grecaptcha.ready(_ => {
    window.grecaptcha
      .execute("_reCAPTCHA_site_key_", { action: "homepage" })
      .then(token => {
        // ...
      })
  })
}

useEffect(() => {
  // Add reCaptcha
  const script = document.createElement("script")
  script.src = "https://www.google.com/recaptcha/api.js?render=_reCAPTCHA_site_key"
  script.addEventListener("load", handleLoaded)
  document.body.appendChild(script)
}, [])

return (
  <div
    className="g-recaptcha"
    data-sitekey="_reCAPTCHA_site_key_"
    data-size="invisible"
  ></div>
)

Upvotes: 66

Carlos Vieira
Carlos Vieira

Reputation: 607

Try this one! https://github.com/t49tran/react-google-recaptcha-v3 npm install react-google-recaptcha-v3

Upvotes: 0

Related Questions