Laravel で reCaptcha v3 を使ってバリデートする

2021年7月18日

ボットによるコメントスパムを避ける方法のひとつに、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 未満の場合にはバリデーションエラーにするようなルールを設定しています。

この記事を書いた人
グッドネイバー

“ Webに悩むお客さまの「よき隣人」でありたい ” をモットーに、Web システム開発(主に Laravel)、Web マーケティング支援の仕事をしています。お仕事のご依頼・ご相談はこちらからお気軽にどうぞ。