Laravel5.7でreCAPTCHAv2を実装する

試作しているLaravel5.7+Vue.jsのWebアプリでreCAPTCHAv2をログイン画面に追加してみたので、何をどうしたのかメモしておきます。 ……まだLaravelの仕組みを良く分かっていないので、書いておかないと後で忘れそうなので。

前提

今回は以下のことを前提とします
  • ログイン画面はphp artisan make:authで生成されたものをベースとするが、カスタマイズを施してあるものとする
    • メールアドレスではなく、ユーザIDを認証に使用
    • Laravel用のreCAPTCHAv2の実装として、anhskohbo/no-captchaを使用

実装

では、実際に行ったことを以下に記します。

composer.json

"require": {
//略
        "anhskohbo/no-captcha": "^3.0",
//略
}
anhskohbo/no-captchaというライブラリを使用することは決まっているので、まずはcomposer require anhskohbo/no-captchaします。

コマンド

続いて以下のコマンドを実行します。
> php artisan vendor:publish --provider="Anhskohbo\NoCaptcha\NoCaptchaServiceProvider"
Copied File [\vendor\anhskohbo\no-captcha\src\config\captcha.php] To [\config\captcha.php]
Publishing complete.
これで設定ファイルをPublishします。 config/captcha.phpに以下の内容がセットされると思います。
<?php

return [
    'secret' => env('NOCAPTCHA_SECRET'),
    'sitekey' => env('NOCAPTCHA_SITEKEY'),
    'options' => [
        'timeout' => 30,
    ],
];

reCAPTCHAサイトでの登録

GoogleのreCAPTCHAサイトで使用するreCAPTCHAの情報を登録します。v2でチェックボックス、ドメインはlocalhostと本番用ドメインの2つを追加で。

.envにサイトキーとシークレットキーを追加

config/captcha.phpで指定された名前でサイトキーとシークレットキーを.envにセットします。
NOCAPTCHA_SITEKEY=xxxxxxxxxxxxxxx
NOCAPTCHA_SECRET=xxxxxxxxxxxxxxx

ルータにルーティングを追加

今回のルーティング(routes/web.php)は下記の通りとします。
Route::get('/', function () {
    return redirect('/home');
})->middleware('auth');

Route::get('/login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('/login', 'Auth\LoginController@login');
ログインフォーム(/login)から自分自身へリクエストを投げて、成功したら/homeへリダイレクト。

login.blade.php

                            <form method="POST" action="{{ route('login') }}" class="container">
                                @csrf

                                <div class="form-row mb-3">
                                    <div class="col">
                                        <label for="userid">{{ __('ユーザID') }}</label>
                                        <input type="text" class="form-control{{ $errors->has('userid') ? ' is-invalid' : '' }}" id="userid" name="userid" required autofocus>

                                        @if ($errors->has('userid'))
                                            <span class="invalid-feedback" role="alert">
                                                <strong>{{ $errors->first('userid') }}</strong>
                                            </span>
                                        @endif
                                    </div>
                                </div>

                                <div class="form-row my-3">
                                    <div class="col">
                                        <label for="password">{{ __('パスワード') }}</label>
                                        <input type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" id="password" required>

                                        @if ($errors->has('password'))
                                            <span class="invalid-feedback" role="alert">
                                                <strong>{{ $errors->first('password') }}</strong>
                                            </span>
                                        @endif
                                    </div>
                                </div>

                                <div class="form-group my-3">
                                    <div class="col">
                                        <label for="recaptcha">{{ __('reCAPTCHA') }}</label>
                                        {!! NoCaptcha::display() !!}
                                         @if ($errors->has('recaptcha'))
                                            <span class="invalid-feedback" role="alert">
                                                <strong>{{ __('reCAPTCHAにチェックを入れて下さい') }}</strong>
                                            </span>
                                        @endif
                                    </div>
                                </div>

                                <div class="form-row my-4">
                                    <div class="col text-center">
                                        <button type="submit" class="btn btn-success shadow-sm px-4" id="sugmit">{{ __('ログイン') }}</button>
                                    </div>
                                </div>
                            </form>
ログインフォームはこのような感じで。仮なのでエラーメッセージは直書き。後でresources/lang/ja/以下にファイルを追加して修正する予定。 肝はreCAPTCHA用の部分の{!! NoCaptcha::display() !!}。これでreCAPTCHAを表示させている。
<script src="./js/app.js"></script>
{!! NoCaptcha::renderJs() !!}
ちなみにJS読み込み部分は上記のように。{!! NoCaptcha::renderJs() !!}でreCAPTCHA用のJSを読み込ませる形に。

LoginController.php

app/Http/Controllers/Auth/LoginController.phpにログインを制御する内容を記述。
<?php

namespace App\Http\Controllers\Auth;

//追加分
use Validator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
//元々あるもの
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
//略
    public function username()
    {
        return 'userid';
    }
    public function login(Request $request)
    {
        //バリデーション
        $validator = Validator::make($request->all(), [
            $this->username() => 'required',
            'password' => 'required',
            'g-recaptcha-response' => 'required|captcha' //reCAPTCHA評価
        ]);

        //認証
        $credentials = $request->only($this->username(), 'password');

        if (!$validator->fails() && Auth::attempt($credentials)) { //バリデーションと認証の両方が成功
            return redirect('/home');
        }
        else { //失敗
            //エラー処理
        }
    }
こんな感じ。不恰好ですが、参考サイトではreCAPTCHAの成否判定にValidatorを使用しており、一方でID・パスワードの認証はAuth::attemptしか思い付かなかったのでこんな実装に。 もっと上手い書き方があるとは思うのですが、これでとりあえず動作要件は満たせました。

参考

Laravelの認証

Laravel + reCAPTCHAv2

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。