Articles
Back to TopMenu...
 

reCAPTCHA v3 with NextJS

Spam and other types of automated abuse are issues that all website owners struggle to cope with. In order to protect the forms on your website, you should make it difficult, if not impossible, to fill out or submit the form by an automated tool while still making it possible for your customers to fill out the form as easily as possible. There are many ways to combat spam and we are going to implement one of them, reCAPTCHA v3, with NextJS. The functionality will be implemented without any external package.

In this article, we assume that you are already familiar with reCAPTCHA v3, NextJS, React, and fetch to call API.

Below are the steps:

  • Sign up for API key pair
  • Create a NextJS Application
  • Add the key pair as environment variables
  • Implementation
    • Client
    • Server
  • Deployment

Sign up for API key pair

To start using reCAPTCHA, we need to sign up for an API key pair for our site. The key pair consists of a site key and secret key.

  • The site key is used to invoke reCAPTCHA service on your site or mobile application.
  • The secret key authorizes communication between your application backend and the reCAPTCHA server to verify the user's response.

The secret key needs to be kept safe for security purposes. It is why we will use environment variables in the next session to store the keys.

To sign up for an API key pair follow the steps below:

  1. Go to http://www.google.com/recaptcha/admin
  2. Click on the plus icon Create
  3. Give Label, choose reCAPTCHA v3 as reCAPTCHA type, add domain names of your site and accept the reCAPTCHA Terms of Service

reCAPTCHA register

  1. Press the Submit button to create the API key pair.

reCAPTCHA Keys

Based on google's recommendations to create a separate key pair for testing environments, we will create two API key pairs, one for testing and one for production.

  Testing Production (*)
Label Local reCAPTCHA - NEXTJS reCAPTCHA - NEXTJS
reCAPTCHA type reCAPTCHA v3 reCAPTCHA v3
Domains localhost url after deployment

* We will use the production key pair during the deployment.

Create a NextJS Application

Since our goal is to implement reCAPTCHA v3 in nextJS, we need a NextJS application. We can create one using create-next-app which automatically configures everything for you. To create the project, run:

$ npx create-next-app recaptcha3-nextjs

Of course, you can also use an old Next.js project.

Add the key pair as environment variables

The key pair is very sensitive from a security point of view, especially the secret key. For this reason, we will be using environment variables to keep them in our application.

  • NEXT_PUBLIC_RECAPTCHA_SITEKEY: will hold the site key and will be exposed to the browser, which is why it is prefixed with NEXT_PUBLIC_.
  • RECAPTCHA_SECRETKEY: will hold the secret key.

This is what .env.local looks like

NEXT_PUBLIC_RECAPTCHA_SITEKEY=6LdKgxxxxxxxxxx_xxxxxx_xxxxxxxxxxxxxxxxx
RECAPTCHA_SECRETKEY=6LdKgxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxx

The key pair in the above file is invalid. Please replace the key pair you generated for the test environment with the key pair from the above file.

In the implementation session, we will access the key pair (site key and secret key) respectivement with process.env.NEXT_PUBLIC_RECAPTCHA_SITEKEY and process.env.RECAPTCHA_SECRETKEY

Implementation

The implementation of reCAPTCHA v3 is divided into two stages. First, with the site key, we get a token from google on the client side of our application. And on the other hand with the secret key we check the token on the server of our application.

Client

Our application in which we will integrate reCAPTCHA, is a single page application with a simple registration form (name and email fields and registration button).

import Proptypes from "prop-types";
import { useState, useEffect } from "react";
import Head from "next/head";
import styles from "../styles/Home.module.css";

export default function Home() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [processing, setProcessing] = useState(false);
  const [completed, setCompleted] = useState(false);
  const [submit, setSubmit] = useState("REGISTER");
  const [result, setResult] = useState({});

  useEffect(() => {
    setSubmit(() => {
      if (completed) return "RESET";
      if (processing) return "PROCESSING";
      return "REGISTER";
    });
  }, [processing, completed]);

  const handleSubmit = (e) => {
    //
    e.preventDefault();

    if (completed) {
      setCompleted(false);
      setName("");
      setEmail("");
      setResult({});
    } else {
      setResult({result: "Just Signed Up"})
    }
  };

  return (
    <>
      <Head>
        <title>reCaptcha v3 with Next.js</title>
        <meta name="description" content="reCaptcha v3 in Next.js" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>reCaptcha v3 with Next.js</h1>
        <p className={styles.description}>
          This shows how to use reCAPTCHA v3 with Next.js without any libraries.
        </p>

        <form onSubmit={handleSubmit} className={styles.form}>
          <div>
            <label htmlFor="name">Name</label>
            <input
              type="text"
              id="name"
              required
              value={name}
              disabled={processing || completed}
              onChange={(e) => setName(e.target.value)}
            />
          </div>
          <div>
            <label htmlFor="email">Email</label>
            <input
              type="email"
              id="email"
              required
              value={email}
              disabled={processing || completed}
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>
          <div>
            <input
              type="submit"
              value={submit}
              disabled={!name || !email || processing}
            />
          </div>
        </form>

        <DisplayResult result={result} />
      </main>

      <footer className={styles.footer}>
        <a
          href="https://github.com/kokou2kpadenou/recaptcha3-nextjs"
          target="_blank"
          rel="noopener noreferrer"
        >
          Source Code
        </a>
      </footer>
    </>
  );
}

const DisplayResult = ({ result }) => (
  <>
    {result && Object.keys(result).length > 0 && result.constructor === Object && (
      <div
        className={styles.output}
        style={{
          backgroundColor: result.success
            ? "rgba(0, 0, 255, 0.1)"
            : "rgba(255, 0, 0, 0.1)",
          borderColor: result.success ? "#00f" : "#f00",
        }}
      >
        <div className={styles.result}>{`Registration ${
          result.success ? "successfull" : "failed"
        }`}</div>
        <strong>Output:</strong>
        <br />
        <pre>{JSON.stringify(result, undefined, 2)}</pre>
      </div>
    )}
  </>
);

DisplayResult.propTypes = {
  result: Proptypes.shape({}).isRequired,
};

At this level, we will need the site key that we stored in the environment variable NEXT_PUBLIC_RECAPTCHA_SITEKEY

...
import styles from "../styles/Home.module.css";

const SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITEKEY;

export default function Home() {
...

Now we need to load the JavaScript API of reCAPTCHA v3 with the site key using the NextJS Head component. Starting with NextJS version v11.0.0, you can also use Script Component instead of Head Component for load performance.

...
  return (
    <>
      <Head>
        <title>reCaptcha v3 with Next.js</title>
        <meta name="description" content="reCaptcha v3 in Next.js" />
        <link rel="icon" href="/favicon.ico" />
        <script
          src={`https://www.google.com/recaptcha/api.js?render=${SITE_KEY}`}
        />
      </Head>
...

Then, when submitting the form, we run the google reCAPTCHA. Then we will send the token we received from Google with our data (name and email) to our API for verifcation.

...
    if (completed) {
      setCompleted(false);
      setName("");
      setEmail("");
      setResult({});
    } else {
      setProcessing(true);

      window.grecaptcha.ready(() => {
        window.grecaptcha
          .execute(SITE_KEY, { action: "submit" })
          .then(async (token) => {
            /* send data to the server */

            const body = {
              name,
              email,
              recaptchaResponse: token,
            };

            try {
              const response = await fetch("/api/register", {
                method: "POST",
                headers: { "Content-Type": "application/json;chaset=utf-8" },
                body: JSON.stringify(body),
              });
              if (response.ok) {
                const json = await response.json();
                setResult(json);
              } else {
                throw new Error(response.statusText);
              }
            } catch (error) {
              setResult({ message: error.message });
            }

            /* End of the sending data */

            setProcessing(false);
            setCompleted(true);
          });
      });
    }
...

The finaly code can be found here

Server

Each reCAPTCHA user response token is valid for two minutes, and can only be verified once to prevent replay attacks. If you need a new token, you can re-run the reCAPTCHA verification.

After you get the response token, you need to verify it within two minutes with reCAPTCHA.

So to verify the token, we will send a request to google reCAPTCHA with our secret key and the token obtained from the client user as the payload. This will be done in our API named register in the /pages/api/register.js file see the content below.

export default async (req, res) => {
  const SECRET_KEY = process.env.RECAPTCHA_SECRETKEY;

  const { name, email, recaptchaResponse } = req.body;

  const verifyUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${recaptchaResponse}`;

  try {
    const recaptchaRes = await fetch(verifyUrl, { method: "POST" });

    const recaptchaJson = await recaptchaRes.json();

    res.status(200).json({ name, email, ...recaptchaJson });
  } catch (e) {
    res.status(400).json(e.error);
  }
};

Deployment

The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
During deployment, we will create the environment variable NEXT_PUBLIC_RECAPTCHA_SITEKEY and RECAPTCHA_SECRETKEY using the production key pair created above.

Check out our Next.js deployment documentation for more details.

Below the result after deployment.

Demo

All the code can be found in this repository and you can see the demo here.