投稿日:2025年10月9日

今回は、LaravelGoogle reCAPTCHAを実装しました。
Laravel 12Google reCAPTCHA v3を例に、その実装方法を紹介したいと思います。

reCAPTCHAキー作成

reCAPTCHAを使用するにはGoogleアカウントが必要です。
あらかじめご用意ください。

Google Cloud にアクセスします。
ログインし右上のドキュメントをクリックします。

190_02

▲左のサイドメニューを下にスクロールします。
セキュリティ > アプリケーションのセキュリティ > reCAPTCHA をクリックします。

▲メイン画面の表示が変わるので下にスクロールすると上図が表示されます。
「ウェブサイトにreCAPTCHAを設定する」をクリックします。

▲表示が変わるので「ウェブサイトのキーを作成する。」をクリックします。

▲「reCAPTCHA管理コンソールに移動」をクリックします。

▲上図のように任意、入力します。
ラベルには、サイトを識別できる内容を入力します。
reCAPTCHAスコアベース(v3)を選びます。
ドメインに入力したドメインと、そのサブドメインがreCAPTCHAの対象になります。
プロジェクト名を入力します、使用できる文字は、英数字、単一引用符、ハイフン、スペース、感嘆符になります。
最後に送信をクリックします。

▲サイトキーとシークレットキーが発行されます。
後ほどLaravel.env(環境設定)に記述するので控えておきます。

Laravel 12の設定

Laravelプロジェクトを事前に作っておきます。
プロジェクトや申し込みフォームの作成は以下のブログ記事を参考にしてください。

ライブラリのインストール

composer require josiasmontag/laravel-recaptchav3

▲composerコマンドでライブラリをインストールします。
reCAPTCHAのライブラリは数種類ありますが、今回はjosiasmontag/aravel-recaptchav3 を使用します。

設定ファイルの作成

php artisan vendor:publish --provider="Lunaweb\RecaptchaV3\Providers\RecaptchaV3ServiceProvider"

▲実行すると config/recaptchav3.php が作成されます。
以下がrecaptchav3.phpの記述になります。

<?php
return [
    'origin' => env('RECAPTCHAV3_ORIGIN', 'https://www.google.com/recaptcha'),
    'sitekey' => env('RECAPTCHAV3_SITEKEY', ''),
    'secret' => env('RECAPTCHAV3_SECRET', ''),
    'locale' => env('RECAPTCHAV3_LOCALE', '')
];

環境設定にキーを記述

RECAPTCHAV3_SITEKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
RECAPTCHAV3_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxx

▲.env(本番環境の.env)ファイルに先ほど発行したサイトキーシークレットキーを記述します。
ちなみにv2用になりますがローカル環境やテスト環境用のキーはGoogleが別途発行 しています。
サイトキー:6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
秘密鍵:6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
v2で作成するときは使用しましょう。

v2のテストキーだとこのように表示されます。

bladeへの記述

layout.blade.php

index(入力画面)、confirm(確認画面)、complete(完了画面)共通のファイルになります。

<!-- 省略 -->
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- CDNやCSSなど -->
    @yield('headScript')
    {!! RecaptchaV3::initJs() !!}
    <title>@yield('title')</title>
</head>
<!-- 省略 -->

▲7行目、Blade記法で {!! RecaptchaV3::initJs() !!} を記述します。
josiasmontag/laravel-recaptchav3 のメソッドで以下を出力します。

<script src="https://www.google.com/recaptcha/api.js?render=サイトキー"></script>
<input type="hidden" name="g-recaptcha-response" value="">

▲1行目でreCAPTCHAのAPIを読み込みます。
2行目の input に、ページ読み込み時、または submit 時にreCAPTCHAのトークンを自動で挿入します。
Laravel 側ではこのトークンを受け取って v3 のスコア検証 を行います。

index.blade.php

<form action="{{ route('form.confirm') }}" method="POST" id="userRegister">
    @csrf
    <table class="frmBlock">
        <!-- お名前 -->
        <tr>
            <th>お名前<span>必須</span></th>
            <td>
                <div class="inptBox">
                    <div>
                        <input id="name" type="text" name="name" size="30" value="{{ old('name') }}">
                        {{-- バリデーション処理 --}}
                        @error('name')
                        <p class="errMessege errName">{{$message}}</p>
                        @enderror
                    </div>
                </div>
            </td>
        </tr>
    </table>

    {!! RecaptchaV3::field('index') !!}

    <input id="submit" type="submit" name="submitBtnVal" value="確認画面へ進む">
</form>

▲21行目の {!! RecaptchaV3::field('index') !!}{!! RecaptchaV3::initJs() !!} とペアで動く重要な記述になります。

Blade 上ではこのように書きますが、実際には以下のような HTML に展開されます

<input type="hidden" name="g-recaptcha-response" data-recaptcha-action="index">

▲name属性値 g-recaptcha-responsereCAPTCHAのトークンが自動的に格納されるフィールド名で、この名前でLaravelに送信されます。
data-recaptcha-action属性値はreCAPTCHA v3 の「アクション名」を指定します。
Google側に「どのページ(indexやconfirm)で発行したトークンか」を伝えるための識別子になります。

confirm.blade.php

<form action="{{ route('form.complete') }}" method="POST" id="userRegister">
    @csrf
    <table class="frmBlock">
        <!-- お名前 -->
        <tr>
            <th>お名前</th>
            <td>
                <div class="inptBox">
                    <div>{{ $contents['name'] }}
                        <input name="name" type="hidden" value="{{ $contents['name'] }}">
                    </div>
                </div>
            </td>
        </tr>
    </table>

    {!! RecaptchaV3::field('confirm') !!}

    <input id="backBtn" type="button" onClick="history.back()" value="戻る">
    <input id="submitConf" type="submit" name="submitBtnVal" value="この内容で送信">
</form>

▲アクション名だけconfirmにする以外は、index.blade.phpと同じになります。

バリデーションへの記述

バリデーションの記述になります。

class AppFormRequest extends FormRequest
{
    // 〜省略〜

    public function rules(): array
    {
        $action = 'index';
        if ($this->routeIs('form.confirm')) {
            $action = 'index';
        } elseif ($this->routeIs('form.complete')) {
            $action = 'confirm';
        }
        return [
            'name' => ['required'],
            'g-recaptcha-response' => "required|recaptchav3:{$action},0.5",
        ];
    }

    public function messages(): array
    {
        return [
            'name.required' => 'お名前 は必須です。',
            'g-recaptcha-response.recaptchav3' => 'reCAPTCHA の検証に失敗しました。再度お試しください。',

        ];
    }
}

▲7〜11行目。表示されているルート(ページ)によって indexconfirm かを判断します。
今どのページにいるかを判定して、次の処理を切り替えるための分岐になります。
つまり、form.confirm だったら index を、form.complete だったら confirm$action に格納します。
必須項目とし、reCAPTCHAの対象とします。
0.5はreCAPTCHAスコア閾値になります。
スコア閾値は0〜1。
0に近いほどbotの可能性が高いと判定され、1に近いほど人間の可能性が高いと判定されます。
23行目はエラー時のメッセージになります。

設定は以上になります。
以下が今回作ったフォームになります。

▲index(入力画面)。
右下にreCAPTCHAが表示されました。

▲confirm(確認画面)。
こちらも表示されてます。

まとめ

普段は申込フォームの制作も行っているため、顧客情報の漏洩を防ぐためのセキュリティ対策には特に注意を払っています。
その観点からも、Laravelを採用することはデファクトスタンダードになっていると言えます。

Laravelでは、CSRF(クロスサイト・リクエスト・フォージェリ)攻撃 を防ぐためにトークンを自動的に生成します。
フォームにアクセスするたびに異なるトークンが発行され、送信時にそのトークンが一致しない場合はエラーが発生し、送信できない仕組みになっています。

顧客情報のCSVにアクセスする際にはBASIC認証を設けるなど、多重防御を行うことで第三者による不正アクセスを防いでいます。
さらに通信経路の暗号化(SSL/TLS)やアクセスログの監視を組み合わせることで、セキュリティレベルを一層高めています。

そして、より強固なセキュリティ対策として、LaravelGoogle reCAPTCHAを実装しした次第です。

今回使用したライブラリ josiasmontag/laravel-recaptchav3 のページにも実装の手順は書いてあります。が、こちらはconfirm(確認画面)を作成しない方法になります。

本記事は参考に留め、ご自身で試行錯誤しながら実装していただきますようお願いいたします。
不備が生じても紹介しているサイトや当方は責任を負いませんのでご了承ください。

最後まで読んでいただきありがとうございました。

Laravelの記事