DEV Community

Cover image for Implementing Google reCAPTCHA v3 with Next.js (React)
Jai
Jai

Posted on

Implementing Google reCAPTCHA v3 with Next.js (React)

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.

  1. Go to the Google reCAPTCHA Admin Console.
  2. Click on + to create a new reCAPTCHA.
  3. Select reCAPTCHA v3 as the version.
  4. Add your domain(s) in the "Domains" section.
  5. 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>'
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
// 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}</>;
}
Enter fullscreen mode Exit fullscreen mode
// 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);
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
  }
};
Enter fullscreen mode Exit fullscreen mode
//Example: add middleware to route
router.post(
  '/reset-password',
  verifyRecaptchaToken,
  async (req, res) => {
    //...
  }
);
Enter fullscreen mode Exit fullscreen mode

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)