Next.jsのWEBサイトにreCAPTCHA(v3)を導入する
はじめに
WEB サイトにはコメント機能や問い合わせフォームが必ずと言ってよいほど存在していますが、何も考えずに設置してしまうと、bot による迷惑メールやスパムを受信する窓口を開けてしまうことになります。
そこで役に立つのが Google の提供するサービスのreCAPTCHAです。
現在 reCAPTCHA には v2
と v3
の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 サービスへ登録します。
詳細は割愛しますが、私の開発環境での登録内容は以下の通りです。
ドメイン
にはサーバーのドメインを入力します。No.4 を処理するサーバーです。
ここでは開発環境の動作確認用サーバーの localhost と、本番用サーバーのドメインを設定しています。
登録を完了させ、設定画面 ⚙ にアクセスするとサイトキー
とシークレットキー
が発行されているので、これをメモしておきます。
No.2~3 フロントエンド側の実装
Next.js のプロジェクトにライブラリをインストールします。
npm install --save react-google-recaptcha-v3
_app.tsx
にて<Component>
を<GoogleReCaptchaProvider>
でラッピングし、reCaptchaKey
にはメモしておいたサイトキー
を代入します。
ここでは、メモした各種キーを環境変数に保存して呼び出しています。
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 する時の送信先と一致させておきましょう。
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
Discussion