前回、入力内容をSlackへ投稿する部分を実装しました。最終回となる今回は、Bot対策のため、フォームにreCAPTCHA v3を導入します。
準備
reCAPCHA作成
reCAPTCHAのサイトで作成します。reCAPTCHAタイプはv3を選択します。
作成出来たらサイトキーとシークレットを控えておきます。

ライブラリインストール
PHP用のクライアントライブラリをcomposerでインストールします。
$ composer require google/recaptcha "^1.2"
実装
環境変数とコンフィグ
.env
にサイトキーとシークレットを追加します(サンプルは適当です)。
RECAPTCHA_SITE_KEY=xxxxxxxxxx
RECAPTCHA_SECRET=xxxxxxxxxx
つぎにconfig/services.php
に以下を追加し、アプリケーションからconfig経由で読み込めるようにします。
'recaptcha' => [
'sitekey' => env('RECAPTCHA_SITE_KEY'),
'secret' => env('RECAPTCHA_SECRET'),
],
ビューの変更
入力画面において、フォーム送信時にreCAPTCHAによってトークンを取得し、それをリクエストに含めるようにします。resources/views/inquiry/index.blade.php
の scriptセクションを以下のように変更します。
@section('script')
<script src="https://www.google.com/recaptcha/api.js?render={{ config('services.recaptcha.sitekey') }}"></script>
<script>
function onClick(e) {
// ボタンを無効に
e.currentTarget.disabled = true;
grecaptcha.ready(function() {
grecaptcha.execute("{{ config('services.recaptcha.sitekey') }}", {action: "submit"}).then(function(token) {
// tokenをフォームにセットして送信
document.getElementById("recaptcha-token").value = token;
document.getElementById("contactform").submit();
});
});
}
</script>
@endsection
この変更で、前に定義していた、<input type="hidden" name="token" id="recaptcha-token" />
の要素にトークンがセットされて、送信されるようになります。
reCAPCHAの検証
結合(ライブラリのインスタンス生成)
インストールしたreCAPTCHAのライブラリ(ReCaptcha\ReCaptcha
クラス)ですが、コンストラクタの第1引数でreCAPTCHAのシークレットを渡す必要がありますので、結合を登録しておきます。今回はapp\Providers\AppServiceProvider.php
で追加しました。
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use ReCaptcha\ReCaptcha;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(ReCaptcha::class, function ($app) {
return new ReCaptcha(config('services.recaptcha.secret'));
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
}
}
カスタムバリデーションクラス
トークンの検証部分ですが、カスタムバリデーションクラスを使います。まず、Artisanで生成します。
$ php artisan make:rule ReCaptcha
app/Rules/ReCaptcha.php
が生成されたと思います。以下のように編集します。
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use ReCaptcha\ReCaptcha as ReCaptchaClient;
class ReCaptcha implements Rule
{
/**
* @var ReCaptchaClient $client
*/
private ReCaptchaClient $client;
/**
* Constructor.
*
* @param ReCaptchaClient $client
* @return void
*/
public function __construct(ReCaptchaClient $client)
{
$this->client = $client;
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
$response = $this->client
->setScoreThreshold(0.3)
->verify($value);
return $response->isSuccess();
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'エラーが発生しました。';
}
}
passes
メソッドでの判定ですが、今回はスコアのみで判定(setScoreThreshold
メソッド)しています。実際に使う場合は、ホスト名(setExpectedHostname
)やアクション(setExpectedAction
メソッド)なども判定に使用すべきかと思います。
フォームリクエストの変更
以前に作成したフォームリクエストApp\Http\Requests\InquiryRequest
クラスを編集し、token
パラメータに対する検証を行うようにします。主な変更箇所はrules
メソッドになります。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use App\Rules\ReCaptcha;
class InquiryRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(ReCaptcha $re_captcha)
{
return [
'name' => ['required', 'max:20'],
'email' => ['required', 'email:rfc'],
'message' => ['required', 'max:1024'],
'token' => ['required', $re_captcha],
];
}
/**
* Get custom messages for validator errors.
*
* @return array
*/
public function messages()
{
return [
'required' => ':attributeは必須です。',
'max' => ':attributeは最大:max文字で入力してください。',
'rfc' => '正しいメールアドレスを入力してください。',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'name' => 'お名前',
'email' => 'メールアドレス',
'message' => 'メッセージ',
'token' => 'ReCaptcha',
];
}
}
実行結果
フォームを表示し、右下にreCAPTCHAのマークが表示されていることを確認します。

普通に入力して送信すると、以前と同じく確認画面へ遷移できることが確認できます。
一方、適正でないと判定されたユーザーの場合、どうなるのでしょうか。reCAPTCHAのスコアを指定はできないので、適正判定のスコアの閾値自体を一時的に変更して確認します。
App\Rules\Recaptcha
クラスのpasses
メソッド内で指定しているスコアの閾値0.3を1.0に変更します。
public function passes($attribute, $value)
{
$response = $this->client
->setScoreThreshold(1.0)
->verify($value);
return $response->isSuccess();
}
この状態ではフォームに入力して送信しても、下図のようにエラーが表示され、確認画面へ遷移できないはずです。

ということで、reCAPTCHAを導入して、Bot対策を講じることができました。