🫠

【NextJS】react-hook-formにreCAPTCHA V2を導入する

2024/07/10に公開

はじめに

Botからの攻撃からフォームを守りたいとき、reCAPTCHAを入れてサイトを守りたい。
そんなときは年に数回程度あるんじゃないでしょうか。
いつでも実装できるようにやり方をまとめておきたいと思います。

概要

Yup × React-hook-formで実装しているフォームにreCAPTCHAを導入します。
実装のための準備と実装とわけて記事にしました。
実装方針が知りたい方は実装の項目から読んでください。

前提条件/仕様

  • Yup × React-hook-formで実装しているフォームに導入する
  • フロント側でreCAPTCHAの認証を行い、tokenを保存する
  • Submit時にtokenをバックエンドに送る
  • バックエンドでトークンを検証する
    • 問題があればBad Requestを返す
    • 問題がなければ保存する
  • Submitまでを検証する

準備

まずはNextJSのプロジェクトを作成して、簡単なフォームを作成する

1.Next.jsのプロジェクトをクリエイトします

terminal
$ pnpm create next-app
✔ What is your project named? … test
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias (@/*)? … Yes
✔ What import alias would you like configured? … @/*

2. 前提条件となるライブラリのインストール

yupとreact-hook-formをインストール

terminal
$ pnpm add yup react-hook-form @hookform/resolvers

3. 立ち上げる

一旦ここで確認しておきます。

terminal
pnpm dev
=> open localhost:3000

4. react-hook-formのExampleを参考にフォームを追加する

簡単なフォームを作成します。

page.tsx
'use client'
import { useForm } from "react-hook-form"
import { yupResolver } from "@hookform/resolvers/yup"
import * as yup from "yup"
import { NextPage } from "next"

const schema = yup
  .object({
    firstName: yup.string().required(),
    age: yup.number().positive().integer().required(),
  })
  .required()

const Page: NextPage = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: yupResolver(schema),
  })
  const onSubmit = (data) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <p>firstName</p>
      <input {...register("firstName")} />
      <p style={{color: 'red'}}>{errors.firstName?.message}</p>

      <p>age</p>
      <input {...register("age")} />
      <p style={{color: 'red'}}>{errors.age?.message}</p>

      <input type="submit" />
    </form>
  )
}

export default Page

参考

https://react-hook-form.com/get-started#SchemaValidation

5. reCAPTCHAのセットアップ

https://www.google.com/recaptcha/about/
開発環境用にlocalhostで登録する
また、保存の先のページのサイトキー、シークレットキーを控えておく

参考

https://www.google.com/recaptcha/about/

6. 【Next.js】reCAPTCHAの基本実装

reCAPTCHAを表示するライブラリを入れる

terminal
$ pnpm add react-google-recaptcha

基本実装

reCAPTCHAのセットアップで取得したsitekeyをいれる
チェックを完了したら、onChangeが発火するようです。

page.tsx
'use client'
import ReCAPTCHA from "react-google-recaptcha";

  const onChange = (value: string) => {
    console.log("Captcha value:", value);
  }

---

  <ReCAPTCHA
    sitekey="your site key"
    onChange={onChange}
  />

基本実装の確認

ここまででフォームにreCAPTCHAのComponentが表示される

$ pnpm dev
=> https://localhost:3000

page.tsx
  const onChange = (value: string) => {
    console.log("Captcha value:", value);
  }

チェックボタンを通すことで
上記の実装部分のvalueにトークンが返ることを確認できる

実装

react-hook-formにreCAPTCHAのトークンを載せる

onChangeの発火時にreact-hook-formに載せようと思う。
setValueを使ってreCAPTCHA用のkeyにセットします。

参考

https://react-hook-form.com/docs/useform/setvalue

Yupとreact-hook-formを含んだ実装

以下の実装で、tokenのkeyに保存することができた

schema

token: yup.string().required()

page.tsx(schema)
const schema = yup
  .object({
    firstName: yup.string().required(),
    age: yup.number().positive().integer().required(),
    token: yup.string().required()
  })
  .required()

onChange

setValue("token", value);

page.tsx(onChange)
  const onChange = (value: string) => {
    setValue("token", value);
    console.log("Captcha value:", value);
  }

確認

チェックできていないとエラーが出ることを確認した
また、タイムアウトでチェックが外れたときもrequiredでエラーになることが確認できた

今回のすべてのコード

実装のすべて

page.tsx
'use client'
import { useForm } from "react-hook-form"
import { yupResolver } from "@hookform/resolvers/yup"
import * as yup from "yup"
import { NextPage } from "next"
import ReCAPTCHA from "react-google-recaptcha";


const schema = yup
  .object({
    firstName: yup.string().required(),
    age: yup.number().positive().integer().required(),
    token: yup.string().required()
  })
  .required()

const Page: NextPage = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
    setValue
  } = useForm({
    resolver: yupResolver(schema),
  })

  const onChange = (value: string) => {
    setValue("token", value);
    console.log("Captcha value:", value);
  }
  const onSubmit = (data) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <p>firstName</p>
      <input {...register("firstName")} />
      <p style={{color: 'red'}}>{errors.firstName?.message}</p>

      <p>age</p>
      <input {...register("age")} />
      <p style={{color: 'red'}}>{errors.age?.message}</p>

      <ReCAPTCHA
        sitekey="your site key"
        onChange={onChange}
      />
       <p style={{color: 'red'}}>{errors.token?.message}</p>
      <input type="submit" />
    </form>
  )
}

export default Page

終わりに

いかがですか。
少し強引な実装になりましたが、これでreCAPTCHAを導入することができるのではないでしょうか。
より良い実装方針がありましたらコメントいただけると嬉しいです。

Discussion