Google reCAPTCHA is a powerful tool for protecting your web applications from spam and abuse. It works by distinguishing between human and bot traffic. reCAPTCHA v3, unlike its predecessors, doesn't require the user to complete challenges such as checking boxes or identifying objects in images. Instead, it runs in the background and assigns a score based on how likely the user is a bot. This is particularly useful for providing a seamless user experience, especially for applications built with modern JavaScript frameworks like Next.js.
Goal of this article -
- Implementing invisible reCAPTCHA for better user experience.
- No external dependencies required.
- Only load scripts for pages that require reCAPTCHA functionality and perform cleanup when not required.
- Reusable backend middleware to verify captcha token in any route.
Prerequisites
- Basic knowledge of React and Next.js.
- Node.js and npm/yarn installed. For this article we will be using Next.js for frontend and Node.js with express framework for backend.
- A Google reCAPTCHA v3 site key and secret key.
Step 1: Get Your Google reCAPTCHA Keys
Before we dive into the code, you'll need to create a Google reCAPTCHA project and get your site key and secret key.
- Go to the Google reCAPTCHA Admin Console.
- Click on + to create a new reCAPTCHA.
- Select reCAPTCHA v3 as the version.
- Add your domain(s) in the "Domains" section.
- Once you register your site, you'll be given a Site Key and a Secret Key.
Step 2: Add Environment
For our Next.js project, we need to add an environment variable for google reCAPTCHA site key :
NEXT_PUBLIC_GOOGLE_RECAPTCHA_SITE_KEY='<your-site-key>'
Step 3: Create the reCAPTCHA Component and helper function.
Next, let's create a reusable React component to handle the reCAPTCHA validation on the client-side.
We will take example of reset password page where email and reCAPTCHA validation is required.
Create a file called ReCaptchaWrapper.js inside the components folder, but before that let us define a state to check if required reCaptcha scripts are loaded successfully.
// store/auth.js
import { atom } from 'jotai'; //for state management
export const isRecaptchaLibLoadedAtom = atom(false);
// components/ReCaptchaWrapper.js
import { useEffect } from 'react';
import { useSetAtom } from 'jotai';
import { isRecaptchaLibLoadedAtom } from '@/store/auth';
export function ReCaptchaWrapper({ children }) {
const setIsRecaptchaLibLoaded = useSetAtom(isRecaptchaLibLoadedAtom);
useEffect(() => {
const loadRecaptchaScript = () => {
const script = document.createElement('script');
script.src = `https://www.google.com/recaptcha/api.js?render=${process.env.NEXT_PUBLIC_GOOGLE_RECAPTCHA_SITE_KEY}`;
script.async = true;
script.onload = () => {
setIsRecaptchaLibLoaded(true);
};
document.body.appendChild(script);
};
loadRecaptchaScript();
//cleanup
return () => {
const script = document.querySelector(
`script[src="https://www.google.com/recaptcha/api.js?render=${process.env.NEXT_PUBLIC_GOOGLE_RECAPTCHA_SITE_KEY}"]`
);
if (script) document.body.removeChild(script);
};
}, []);
return <>{children}</>;
}
// utils/formUtils.js
export function getCaptchaToken(actionName) {
return new Promise((resolve, reject) => {
window.grecaptcha.ready(() => {
window.grecaptcha
.execute(process.env.NEXT_PUBLIC_GOOGLE_RECAPTCHA_SITE_KEY, { action: actionName })
.then(resolve)
.catch(reject);
});
});
}
Explanation:
ReCaptchaWrapper: This components acts as a provider for pages which require reCAPTCHA functionality. It gives us access to the getCaptchaToken function, which we use to trigger reCAPTCHA verification on the client-side.
getCaptchaToken: This function triggers the reCAPTCHA verification for a given action. When called, it will return a reCAPTCHA token that will be sent to the server to validate.
Step 4: Integrate reCAPTCHA into Your Form
Now that we have the reCAPTCHA component ready, let's integrate it into a reset-password form. We'll create a form where users can submit a message. After the user clicks the submit button, we'll validate the reCAPTCHA token.
Here's an example of a simple contact form using reCAPTCHA:
// pages/reset-password.js
import { Formik, Form, Field } from "formik";
import ReCaptchaWrapper from "@/components/ReCaptchaWrapper";
import { Button } from "@/components/Button";
import { getCaptchaToken } from "@/utils/formUtils"; //helper function
//to make sure if scripts are loaded
import { isRecaptchaLibLoadedAtom } from "@/store/auth";
import { useAtomValue } from "jotai";
const ResetPasswordPage = () => {
const isRecaptchaLibLoaded = useAtomValue(isRecaptchaLibLoadedAtom);
return (
<ReCaptchaWrapper>
<Formik
onSubmit={async () => {
if (!isRecaptchaLibLoaded) {
console.error("reCAPTCHA is not ready");
return;
}
const token = await getCaptchaToken("reset-password");
//submit token along with email via POST method to backend
//for verify captcha token
}}
>
{({ isSubmitting, handleChange }) => (
<Form className="space-y-8">
<Field name="email">
{({ field }) => (
<input
{...field}
autoComplete="email"
onChange={(e) => {
handleChange(e);
}}
placeholder="Your email address"
/>
)}
</Field>
<Button type="submit" disabled={isSubmitting}>
Reset Password
</Button>
</Form>
)}
</Formik>
</ReCaptchaWrapper>
);
};
export default ResetPasswordPage;
Step 5: Verify reCAPTCHA Token on the Server
On the server-side, we need to verify the reCAPTCHA token to ensure it is valid. To do this, we'll create an middleware in Node.js (Express application) + Typescript that sends the token to Google's verification endpoint.
//middleware/recaptcha.ts
import axios, { AxiosRequestConfig } from "axios"; //axios: "^0.21.4"
const SECRET_KEY = "YOUR_GOOGLE_RECAPTCHA_SECRET_KEY"; // load it from dotenv
const verifyRecaptcha = async (token) => {
const requestConfig = {
method: "POST",
url: "https://www.google.com/recaptcha/api/siteverify",
params: {
secret: SECRET_KEY,
response: token,
},
};
const response = await axios(requestConfig as AxiosRequestConfig);
return response.data;
};
// add this middleware to route
export const verifyRecaptchaToken = async (req, res, next) => {
const response = await verifyRecaptcha(req.body.recaptchaToken);
const { success } = response;
if (success) {
next();
} else {
// throw new Error('Unauthorized', 400);
}
};
//Example: add middleware to route
router.post(
'/reset-password',
verifyRecaptchaToken,
async (req, res) => {
//...
}
);
Explanation:
verifyRecaptcha: This function sends the reCAPTCHA token to Google's verification API, passing the secret key and token. It returns true if the token is valid.
If the reCAPTCHA verification fails, we send a error response. Otherwise, we proceed with handling the form data.
Step 6: Test the Integration
Now, you can test the integration! When you submit the form, Google's reCAPTCHA will be triggered in the background. The token will be sent to your server for verification, and only if the token is valid will the form be processed.
If reCAPTCHA fails (for example, if it's a bot), the user will see an error message.
Conclusion
By integrating Google reCAPTCHA v3 with Next.js, you can effectively prevent spam and abuse while providing a smooth, seamless user experience.
Top comments (0)