Laravel で reCaptcha v3 を使ってバリデートする
ボットによるコメントスパムを避ける方法のひとつに、reCAPTCHA を利用するというものがあります。Google が提供していて、現在はユーザー側の操作が不要な v3 と、「私はロボットではありません」にチェックを入れて先に進む v2 があります(両者の違いはこちら)。
Laravel に簡単に組み込むためのパッケージもいくつか公開されていますが、今回は reCAPTCHA v3 に対応し、複数の Laravel のバージョンに対応している ARCANEDEV/noCAPTCHA を使ってみました。
インストールと設定
インストールは composer を使って行いますが、Laravel 6 で使う場合には、バージョン10系を使う必要があるので、以下のようにバージョンを指定します。
composer require arcanedev/no-captcha:"^10.0"
Google 側でサイトを登録した際に表示されるサイトキーとシークレットキーは、.env に記載します。
NOCAPTCHA_SITEKEY=xxxxxxxxxxxxxxxxxxxxx
NOCAPTCHA_SECRET=xxxxxxxxxxxxxxxxxxxxx
また、ARCANEDEV/noCAPTCHA 用の設定ファイルを config ディレクトリ内に作成するために
php artisan vendor:publish --provider="Arcanedev\NoCaptcha\NoCaptchaServiceProvider"
を実行します。config/no-captcha.php
が生成されますが、v3 を使うのであれば、修正は特に不要です。
ビューの作成
設定が終わったら、ビューを作成していきます。といっても、ビュー側はさほど楽できる部分はありません。reCAPTCHA v3 のトークンは、発行されてから2分で有効期限が切れてしまうため、フォームで利用する場合には、下のコードのように、フォームの submit 時にトークンを発行するようにしたほうがよいかもしれません。{!! no_captcha()->input() !!}
は、<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">
に置き換えられます。
<form action="/inquiry/confirm" method="post" id="inquiry_form">
// フォームの内容を書いて...
<button class="btn btn-primary" id="submit_btn">入力内容を確認する</button>
{!! no_captcha()->input() !!}
@csrf
</form>
{!! no_captcha()->script() !!}
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript">
$('#submit_btn').click(function () {
grecaptcha.ready(function () {
grecaptcha.execute('{{config('no-captcha.sitekey')}}', {action: 'submit'}).then(function (token) {
$('#g-recaptcha-response').val(token)
$('#inquiry_form').submit()
})
})
})
</script>
余談ですが、grecaptcha.execute
の引数である action: 'submit'
の submit の部分には好きな文字列を指定して構いません。ここで指定した文字列は、管理コンソールでの分類に使われるだけで、特に意味があるわけではないからです。ドキュメントで近くに記載されているためか、homepage や login などから選ぶように書かれているサイトが散見されたので。
バリデーション用のフォームリクエストの作成
次に、バリデーション用のフォームリクエストを作成します。前回の記事でフォームリクエストについて説明しましたが、こういうときこそ、prepareForValidation
メソッドの出番です。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Validator;
class InquiryRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => ['required'],
// 他の項目のバリデーションルール
'recaptcha_success' => ['required', 'accepted'],
'recaptcha_score' => ['required', 'numeric', 'gte:0.5'],
];
}
/**
* @return void
*/
protected function prepareForValidation()
{
$response = no_captcha()->verify($this->{'g-recaptcha-response'});
$this->merge([
'recaptcha_success' => $response->isSuccess(),
'recaptcha_score' => $response->getScore()
]);
}
}
フォームから送られてきたトークンを Google のサーバに送って検証することで、スコアなどを取得できます。同じトークンは1回しか検証できない(複数回検証するとエラーになる)ので、no_captcha()->verify($this->{'g-recaptcha-response'})
で結果を取得してから、バリデーションに必要な項目を適宜 merge していきます。
reCAPTCHA v3 は、0.0から1.0の間でスコアを付けるだけで(0.0に近いほどボットの可能性が高い)、アクセスの制御などはしないので、必要に応じて処理を書く必要があります。上の例では、スコアが 0.5 未満の場合にはバリデーションエラーにするようなルールを設定しています。
ディスカッション
コメント一覧
js側の17行目に ); が無くて動きませんでした。
入れたらスムーズに動くようになりました。
コメントありがとうございます。確かに最後の ) が足りないですね。
修正しておきました