🌀
reCAPTCHA v3を Next.js Railsに導入する方法
はじめに
自分は大学の授業レビューサイトを運営しています。
この前、ふと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スクリプトを読み込みます。
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
おわりに
Next.js、Railsの環境でreCAPTCHAを導入するという記事が少なかったため投稿しました。少しでもお力になれれば幸いです。
参考URL
Discussion