はじめに
自分は大学の授業レビューサイトを運営しています。
この前、ふとbotに大量にレビューを登録されてしまったらどうしよう!と思いました。大量にレビューは獲得できますが、全く参考になりませんし、サーバーがダウンしてしまいます。
そこでreCAPTCHAを導入することにしました。
今回の環境
- nextjs 13.4.19
- App router
- Rails 7.0.6
- apiモード
目次
reCAPTCHAとは
普段フォームを送信する際に「私はロボットではありません」というボタンや画像認証を見たことがないでしょうか?まさにそれです。これによってbotかどうか判別することで攻撃から防ぐことができます。
画像認証を行うものがv2で、最近主流になっているv3は、ユーザーの行動を自動で判別するため、ユーザーが何も操作する必要がありません
レビューサイトではレビューのしやすさが重要なので、今回はそのv3を導入していきます。
Google-reCAPTCHAの設定
1 . Google reCAPTCHA管理コンソールにアクセスします
2 . 「新しいサイトを登録する」で以下を登録します。
- ラベル
- reCAPRCHAタイプ。今回はv3
- ドメイン
- ドメインの先頭にhttpsはつけられません
- ローカルのIPアドレス、127.0.0.1も登録しておきましょう!
3 . サイトキーとシークレットキーをメモします。これらは後ほど使用します。
フロントエンド(Next.js)の実装
reCAPTCHAスクリプトの読み込み
Nextjs13のApp routerの場合、app/layout.jsまたはapp/layout.tsxにスクリプトを追加します。next/scriptを使用してGoogleのreCAPTCHAスクリプトを読み込みます。
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を取得できます。
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をポストするように変更しました。
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の追跡対象から除外するようにしましょう。
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=your_site_key
バックエンド(Rails)の実装
必要なgemを導入します。以下をGemfileに記載してください。
- httparty: reCAPTCHAのサービスクラスの導入で使用します
- dotenv-rails: 環境変数を使用するため、必要です。
gem 'httparty'
gem 'dotenv-rails'
RecaptchaVerifierサービスクラスの作成
- attr_reader :score, :action はインスタンス変数 score と action に対する読み取り専用のメソッドを提供し、外部からこの2つの値にアクセスできるようにします。
- initializeメソッドで認証に使用するオブジェクトの初期化を行います。
- verifyでGoogle reCAPTCHA APIにPOSTリクエストを送りスコアを判定します。
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トークンの検証をします。
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に記載しましょう。
RECAPTCHA_SECRET_KEY=your_secret_key
デプロイ後の注意
環境変数を管理サイトで設定しましょう!
ローカルで動いていてもreCAPTCHのロゴが表示されない方はまさに環境変数の設定が原因だと思います。
自分はvercel、Herokuでデプロイをしていましたがどちらも管理サイトで環境変数(.env、.env.local)を設定する必要がありました。
おわりに
Next.js、Railsの環境でreCAPTCHAを導入するという記事が少なかったため投稿しました。少しでもお力になれれば幸いです。
参考URL