0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

reCAPTCHA v3を Next.js Railsに導入する方法

Posted at

はじめに

自分は大学の授業レビューサイトを運営しています。
この前、ふとbotに大量にレビューを登録されてしまったらどうしよう!と思いました。大量にレビューは獲得できますが、全く参考になりませんし、サーバーがダウンしてしまいます。
そこでreCAPTCHAを導入することにしました。

今回の環境

  • nextjs 13.4.19
    • App router
  • Rails 7.0.6
    • apiモード

目次

  1. reCAPTCHAとは
  2. Google-reCAPTCHAの設定
  3. フロントエンド(Next.js)の実装
  4. バックエンド(Rails)の実装
  5. デプロイ後の注意
  6. おわりに
  7. 参考URL

reCAPTCHAとは

普段フォームを送信する際に「私はロボットではありません」というボタンや画像認証を見たことがないでしょうか?まさにそれです。これによってbotかどうか判別することで攻撃から防ぐことができます。

画像認証を行うものがv2で、最近主流になっているv3は、ユーザーの行動を自動で判別するため、ユーザーが何も操作する必要がありません
レビューサイトではレビューのしやすさが重要なので、今回はそのv3を導入していきます。

image.png

image.png

画像の引用元

Google-reCAPTCHAの設定

1 . Google reCAPTCHA管理コンソールにアクセスします

2 . 「新しいサイトを登録する」で以下を登録します。

  • ラベル
  • reCAPRCHAタイプ。今回はv3
  • ドメイン
    • ドメインの先頭にhttpsはつけられません
    • ローカルのIPアドレス、127.0.0.1も登録しておきましょう!

3 . サイトキーシークレットキーをメモします。これらは後ほど使用します。

Screenshot 2024-10-18 at 21.25.06.png

フロントエンド(Next.js)の実装

reCAPTCHAスクリプトの読み込み

Nextjs13のApp routerの場合、app/layout.jsまたはapp/layout.tsxにスクリプトを追加します。next/scriptを使用してGoogleのreCAPTCHAスクリプトを読み込みます。

app/layout.jsx
import './globals.css';
import Script from 'next/script';

export const metadata = {
  title: 'My App',
  description: 'Next.js 13 with Rails API',
};

export default function RootLayout({ children }) {
  const SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;

  return (
    <html lang="ja">
      <head>
        <Script
          src={`https://www.google.com/recaptcha/api.js?render=${SITE_KEY}`}
          strategy="beforeInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

formページへのreCAPTCHA実装

  • window.grecaptchaが存在しない場合、つまりreCAPTCHAのスクリプトが読み込まれていない場合は、エラーメッセージを表示して処理を終了させるううにしています。
  • window.grecaptcha.executeでtokenを取得できます。
app/lectures/[id]/new/page.tsx
  const Submit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const errors = validateReview(review);
    if (!isEmptyObject(errors)) {
      setFormErrors(errors);
      return;
    }

+    if (!window.grecaptcha) {
+      setFormErrors({ recaptcha: 'reCAPTCHAが読み込まれていません。' +});
+      return;
+    }
+
+    try {
+      const SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!;
+      const token = await window.grecaptcha.execute(SITE_KEY, { action: 'submit' });
+      addReview({ ...review, rating: value }, token);
+    } catch (error) {
+      setFormErrors({ recaptcha: 'reCAPTCHAの取得に失敗しまし+た。' });
+    }
+  };
  • reviewに加えてtokenをポストするように変更しました。
app/lectures/[id]/new/page.tsx
  const addReview = async (newReview: ReviewData, token: string) => {
    try {
      const res = await axios.post(`${process.env.NEXT_PUBLIC_ENV}/api/v1/lectures/${params.id}/reviews`, {
        review: newReview,
+        token,
      }, {
        headers: {
          'Content-Type': 'application/json',
        },
      });
  • reCAPTCHAのサイトキーを設定します。
  • Next.jsでは.env.localで基本的に定義するようです。
  • サイトキーは漏洩してはいけないので、gitignoreに記載してgitの追跡対象から除外するようにしましょう。
.env.local
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your_site_key

バックエンド(Rails)の実装

必要なgemを導入します。以下をGemfileに記載してください。

  • httparty: reCAPTCHAのサービスクラスの導入で使用します
  • dotenv-rails: 環境変数を使用するため、必要です。
Gemfile
gem 'httparty'
gem 'dotenv-rails'

RecaptchaVerifierサービスクラスの作成

  • attr_reader :score, :action はインスタンス変数 score と action に対する読み取り専用のメソッドを提供し、外部からこの2つの値にアクセスできるようにします。
  • initializeメソッドで認証に使用するオブジェクトの初期化を行います。
  • verifyでGoogle reCAPTCHA APIにPOSTリクエストを送りスコアを判定します。
app/services/recaptcha_verifier.rb
require 'httparty'

class RecaptchaVerifier
  RECAPTCHA_SECRET_KEY = ENV['RECAPTCHA_SECRET_KEY']

  attr_reader :score, :action

  def initialize(token, expected_action = 'submit', minimum_score = 0.5)
    @token = token
    @expected_action = expected_action
    @minimum_score = minimum_score
    @score = 0.0
    @action = ''
  end

  def verify
    response = HTTParty.post(
      "https://www.google.com/recaptcha/api/siteverify",
      body: {
        secret: RECAPTCHA_SECRET_KEY,
        response: @token
      }
    )

    result = JSON.parse(response.body)

    if result['success'] && result['action'] == @expected_action && result['score'] >= @minimum_score
      @score = result['score']
      @action = result['action']
      true
    else
      false
    end
  rescue StandardError => e
    false
  end
end

ReviewsControllerの実装

  • RecaptchaVerifier クラスのインスタンスを作成し、reCAPTCHAトークンの検証をします。
app/controllers/api/v1/reviews_controller.rb
  def create
    permitted = params.permit(:token, review: [
      :rating, :content, :period_year, :period_term, :textbook, :attendance,
      :grading_type, :content_difficulty, :content_quality
    ])

    token = permitted[:token]
    review_attributes = permitted[:review]

    verifier = RecaptchaVerifier.new(token, 'submit', 0.5)

    if verifier.verify
      create_review(review_attributes)
    else
      render json: { success: false, message: 'reCAPTCHA認証に失敗しました。' }, status: :unprocessable_entity
    end
  end

envファイルを作成

  • こちらもgitignoreに記載しましょう。
.env
RECAPTCHA_SECRET_KEY=your_secret_key

デプロイ後の注意

環境変数を管理サイトで設定しましょう!
ローカルで動いていてもreCAPTCHのロゴが表示されない方はまさに環境変数の設定が原因だと思います。
自分はvercel、Herokuでデプロイをしていましたがどちらも管理サイトで環境変数(.env、.env.local)を設定する必要がありました。

おわりに

Next.js、Railsの環境でreCAPTCHAを導入するという記事が少なかったため投稿しました。少しでもお力になれれば幸いです。

参考URL

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?