🤖

LaravelにreCAPTCHA (v3) を導入する

2023/05/17に公開

はじめに

この記事では「えんさがそっ♪」の LaravelアプリケーションにreCAPTCHA( v3 )を導入する方法についてまとめています。

v2とv3の違いについて

reCAPTCHAには v2 と v3 の 2 種類存在し、認証方法が異なります。

Ver 内容
v2 人間かbotか怪しいリクエストがきた時に、画像やチェックボックスによる認証をさせて、botをシャットダウンしようとします。
v3 認証機能がなく、全てのリクエストを通してスコア判定します。bot判定されたユーザーを拒否するには追加の設定が必要になります。

今回は v3 を採用しており、フォームの不正送信を防ぐためにreCAPTCHA側が設定したスコア判定によって、エラー表示をさせるところまでを説明していきます。

環境

  • Laravel v9.52
  • arcanedev/no-captcha v13.0

導入手順

Google reCAPTCHAの登録

reCAPTCHAの Admin Console から登録してください。

登録後にシークレットキーサイトキーが発行されます。後述の環境変数の設定で使用するので、控えておいてください。


「arcanedev/no-captcha」のライブラリをインストール

自前で構築することもできますが、「環境構築が比較的簡単」、「他サイトでの使用実績も多い」ということで arcanedev/no-captcha を採用しました。

composer require arcanedev/no-captcha:"^13.0"

環境変数(env)設定

先ほど発行したシークレットキーサイトキーを記入します。

NOCAPTCHA_SECRET=XXXXXXXXXXXXXXX
NOCAPTCHA_SITEKEY=XXXXXXXXXXXXXXX

configファイルを作成

/config/no-captcha.phpにconfigファイルを作成します。

<?php

return [

    /* -----------------------------------------------------------------
     |  Credentials
     | -----------------------------------------------------------------
     */

    'secret'  => env('NOCAPTCHA_SECRET', 'no-captcha-secret'),
    'sitekey' => env('NOCAPTCHA_SITEKEY', 'no-captcha-sitekey'),

    /* -----------------------------------------------------------------
     |  Version
     | -----------------------------------------------------------------
     |  Supported: v3, v2
     */

    'version' => 'v3',

    /* -----------------------------------------------------------------
     |  Localization
     | -----------------------------------------------------------------
     */

    'lang' => 'jp',

    /* -----------------------------------------------------------------
     |  Skip IPs
     | -----------------------------------------------------------------
     */

    'skip-ips' => [
        // 127.0.0.1
    ],

];

フロントエンドの実装

formタグの中に{!! no_captcha()->input() !!}を追加します。

    <form action="/complete" method="post" id="form">
        {{ csrf_field() }}
        <dl>
            <dt>名前</dt>
            <dd><input type="text" name="name"></dd>
            <dt>お問い合わせ内容</dt>
            <dd><textarea name="content" /></dd>
        </dl>

	//reCAPTCHAのエラーが発生した時のエラーメッセージ
	//エラーの種類問わず、共通のメッセージを表示しています
        @if(session('isRecaptchaError'))
            <span class="error__message">メッセージの送信に失敗しました。</span>
        @endif
        
        <button type="submit" id="submit_button">送信する</button>
        {!! no_captcha()->input() !!}
    </form>

 
フッターにreCAPTCHAのスクリプトを追加します。
no_captcha()->script()no_captcha()->getApiScript()はscriptタグが含まれているのでエスケープ処理を入れてます。

    {!! no_captcha()->script() !!}
    {!! no_captcha()->getApiScript() !!}
    <script>
        const btn = document.getElementById('submit_button');
        btn.addEventListener('click', function (e) {
            e.preventDefault();
            grecaptcha.ready(function () {
                const siteKey = {{ Js::from(config('no-captcha.sitekey')) }}
                grecaptcha.execute(siteKey,  {action: 'Submit'}).then(function (token) {
                    document.getElementById('g-recaptcha-response').value = token;
                    document.getElementById('form').submit();
                })
            })
        }, false);
    </script>

エラー処理の設定

reCAPTCHAによるエラーが発生した時は、Controllerにリダイレクト処理を追加します。

public function complete(Request $request): View|RedirectResponse
{
    if ($this->hasReCaptchaError($request)) {
        return redirect()->back()->withInput($request->all())->with('isRecaptchaError', true);
    }
	
    --- 以下省略 ---
	
    return view('complete');
}

 
同Controllerにエラー判定のメソッドhasReCaptchaErrorを追加します。
isSuccess:トークン認証やreCAPTCHAへのアクセスに失敗した時はFalse、それ以外はTrue
getScore:reCAPTCHAが設定したスコアが入ります(閾値範囲 0.0 ~ 1.0)

private function hasReCaptchaError(Request $request): bool
{
    /** @var Response $recaptchaResponse */
    $recaptchaResponse = no_captcha()->verify(strval($request->input('g-recaptcha-response')));

    if (!$recaptchaResponse->isSuccess()) {
       return true;
    }

    if ($recaptchaResponse->getScore() <= 0.1) {
       return true;
    }

    return false;
}

今回は下記いずれかが当てはまった場合にエラーとしています。

  1. $recaptchaResponse->isSuccess()がFalse
  2. $recaptchaResponse->getScore()が0.1以下

まとめ

reCAPTCHAは比較的工数をかけずにフォームの不正送信を防ぐことができるのが魅力です。
他にも代表的な実装例としてログインフォームが挙げられます。

最後までお読みいただきありがとうございました!

参考記事

BABYJOB テックブログ

Discussion