Reputation: 1103
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
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
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();
})
Upvotes: 4
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:
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
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
Reputation: 607
Try this one! https://github.com/t49tran/react-google-recaptcha-v3 npm install react-google-recaptcha-v3
Upvotes: 0