🔒

Next.jsのWEBサイトにreCAPTCHA(v3)を導入する

2022/08/15に公開

はじめに

WEB サイトにはコメント機能や問い合わせフォームが必ずと言ってよいほど存在していますが、何も考えずに設置してしまうと、bot による迷惑メールやスパムを受信する窓口を開けてしまうことになります。

そこで役に立つのが Google の提供するサービスのreCAPTCHAです。
https://www.google.com/recaptcha/about/

現在 reCAPTCHA には v2v3 の2つのバージョンが提供されており、v2 は以下の画像のようにユーザーにパズルを解かせて bot でないか判断する仕組みになっています。パズルを解いた事がある方も多いと思います。

一方 v3では、ブラウザ上でのユーザー操作の挙動から自動的に判断するため、v2 のようにユーザーにパズルをさせる必要がありません。すごいですね。

今回は Next.js で制作した WEB サイトの問い合わせフォームに、reCAPTCHA(v3)を導入する方法についてまとめたいと思います。

全体の流れ

reCAPTCHA 認証の全体的な流れを説明します。

No 処理担当 処理
1 事前準備 Google の reCAPTCHA サービスへ登録し、サイトキーシークレットキーを取得する
2 クライアント クライアント側からサイトキーを含めて reCAPTCHA 認証サーバーに POST し、tokenを取得する。
3 クライアント クライアント側からサーバー側にtokenを送る
4 サーバー サーバー側からtokenシークレットキーを含めて reCAPTCHA 認証サーバーに POST し、bot 判定結果を取得する
5 サーバー bot 判定結果により任意の処理を行う

No.1   reCAPTCHA サービスへの登録

Google の reCAPTCHA サービスへ登録します。
https://www.google.com/recaptcha/about/

詳細は割愛しますが、私の開発環境での登録内容は以下の通りです。

ドメインにはサーバーのドメインを入力します。No.4 を処理するサーバーです。
ここでは開発環境の動作確認用サーバーの localhost と、本番用サーバーのドメインを設定しています。

登録を完了させ、設定画面 ⚙ にアクセスするとサイトキーシークレットキーが発行されているので、これをメモしておきます。

No.2~3  フロントエンド側の実装

Next.js のプロジェクトにライブラリをインストールします。

npm install --save react-google-recaptcha-v3

_app.tsxにて<Component><GoogleReCaptchaProvider>でラッピングし、reCaptchaKeyにはメモしておいたサイトキーを代入します。
ここでは、メモした各種キーを環境変数に保存して呼び出しています。

_app.tsx
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <GoogleReCaptchaProvider
      reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_CLIENT_KEY!}
      language="ja"
    >
      <Component {...pageProps} />
    </GoogleReCaptchaProvider>
  );
}

export default MyApp;

No.2 と No.3 の処理を記述します。
この処理を実装する場所としては、例えば問い合わせフォームの送信ボタンの onClick イベントハンドラ関数内などが考えられます。

// reCAPTCHAからtokenを取得する No.2の処理
const { executeRecaptcha } = useGoogleReCaptcha();
token = await executeRecaptcha("Contact");
// サーバーへrecaptcha認証および問い合わせ内容送信処理の要求をPOSTする No.3の処理
const serverEndpoint = "api/recaptcha";
const responce_server = await fetch(serverEndpoint, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    token: token,
  }),
});

No.4 サーバー側の実装

Next.js は、フロントエンド側からアクセス可能な API を同プロジェクト内で記述することができるAPI Routesという機能があります。
つまり、バックエンド側の処理も同プロジェクト内で記述が可能です。今回はこの機能を利用してサーバー側の処理を実装します。

プロジェクトに/pages/api/recaptcha.tsを作成して以下を記述します。
API Routesの仕組みで、処理が呼び出されるサーバー側のエンドポイントは/api/recaptchaになります。No.3 の処理で POST する時の送信先と一致させておきましょう。

/pages/api/recaptcha.ts
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<クライアントに応答する型を定義>
) {
  // reCAPTCHA認証サーバーに認証リクエストをPOSTし、認証結果を受け取る
  const serverSecretKey = `secret=${process.env.RECAPTCHA_SERVER_SECRET_KEY}&response=${req.body.token}`;
  const responce_recaptcha = await fetch(
    "https://www.google.com/recaptcha/api/siteverify",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: serverSecretKey,
    }
  );
  const responceJson_recaptcha = await responce_recaptcha.json();
  console.log(responceJson_recaptcha);

}

reCAPTCHA 認証サーバから次のような応答が帰ってきます。

{
  success: true,
  challenge_ts: '2022-08-15T01:57:53Z',
  hostname: 'localhost',
  score: 0.9,
  action: 'Contaåct'
}
フィールド名 内容
success trueであれば、bot ではないと判断されたことを意味します。
challenge_ts 判定した時刻です。
hostname No.4 の処理を行ったサーバーのホスト名です。
score bot 度合いを示すスコアです。0〜1 の範囲で、1 に近いほど人間の可能性が高いことを意味します。
action No.2 の処理でトークンを取得するexecuteRecaptcha関数の引数とリンクしています。
error-codes 認証でエラーが発生したときに、本フィールドが返されます。

No.5 任意の処理を実装

あとはsuccessを if-else 文の条件として、認証に成功した時と失敗した時の任意の処理を記述すれば完成です。例えば問い合わせフォームの場合、以下のような処理が考えられます。

  • 認証成功 : 問い合わせ内容のメールを作成してサイト管理者に送信し、クライアント側に 200(OK)レスポンスを応答する。(クライアント側それを受けてブラウザ上に成功ポップアップを表示する。)
  • 認証失敗 : クライアント側に 400(BadRequest)レスポンスを応答する。(クライアント側はそれを受けてブラウザ上に失敗ポップアップを表示する)

また、successではなくscoreを if-else 文の条件とすれば、よりシビアな bot 判定ができるかもしれません。ブラウザから 30 回ほど認証を試行したところscoreは全て 0.9 で応答されました。私の人間らしさはどうやら0.9 のようです。

おわりに

Next.js で reCAPTCHA 認証をほぼ最小構成で実装しました。

余談ですが、デバックで何度もブラウザをリロードしていたら score が 0.9 から 0.7 に下がったことがありました。ちゃんと bot らしい挙動を見張ってくれているような気がします。

参考 URL

https://developers.google.com/recaptcha/intro

https://github.com/t49tran/react-google-recaptcha-v3

https://brainlog.jp/programming/post-2567/

https://qiita.com/akki-memo/items/858f1e9ba6bad9e490d8

Discussion