Laravelの認証jwt-authにログイン失敗時のロックアウト機能を追加し、セキュリティを高める方法


jwt-authにロックアウト機能を追加してセキュリティを高めましょう。

最近ではフロントエンドとバックエンドを疎結合するのがモダンな実装なので、
フロントエンドはvue,nuxtなどのSPAを使い、LaravelをAPIサーバとして使うパターンが多いのではないでしょうか。

nuxtやvueからLaravelにログインする際、APIの認証(ログイン機能)としてよく使われるのがjwt-authです。
https://github.com/tymondesigns/jwt-auth

JWT認証をLaravelで導入する手順については、いろんな記事があるのですが、
セキュリティを強化するため、jwt-authの標準では搭載されていない認証に複数回失敗した時のロックアウト機能を追加しました。

jwt-authの導入

LaravelへのAPI認証実装とjwt-authの導入についてはこれらの記事がわかりやすいです。
APIでログインしてbearerトークンを返し、フロントエンドのSPAなどから利用する時に使います。

https://qiita.com/zaburo/items/176f907ea46767283785
https://qiita.com/nozaki-sankosc/items/7ed320d6549f5f92b9b9

Bearer認証について

laravel標準のログインにはロックアウト機能があるのだが・・・

laravelの標準ログイン機能には、失敗した時にロックアウトする機能が標準搭載されています。素敵ですね。

laravel/app/Http/Controllers/Auth/LoginController.php
    protected $maxAttempts = 2;     // ログイン試行回数(回)
    protected $decayMinutes = 10;   // ログインロックタイム(分)

こんな風に2つの変数を追加するだけでロックアウト機能を有効にしてくれるナイスな機能なのですが、残念ながらjwt-authを使っている場合、この変数を追加するだけではもちろん使えません。

jwt-authでもロックアウト機能を使う方法

こちらの記事のようにAPIログインしtokenを返す機能を実装した場合のlogin()メソッドは以下のようになります。

//id,pwで認証してtokenを発行
    function login(){

        $credentials = request(['email', 'password']);

        //もし認証エラーなら
        if(!$token = auth('api')->attempt($credentials)){
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        //OKならtoken発行
        return $this->respondWithToken($token);
    }

このメソッドを以下のように修正しました。

    use AuthenticatesUsers;
    protected $maxAttempts = 3;
    protected $decayMinutes = 1;

    public function login(Request $request)
    {
        $credentials = $request->input(['email', 'password']);

        // ログイン失敗回数をチェックして、制限を超えている場合はロックアウト
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);
            return $this->sendLockoutResponse($request);
        }

        // ログインに失敗した場合のみ、試行回数を増やす
        // 成功した場合はカウントしない。
        if (!$token = auth('api')->attempt($credentials)) {
            $this->incrementLoginAttempts($request);
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        // ログインに成功した場合は、制限をクリアする(お好みで)
        $this->clearLoginAttempts($request);
        $result = $this->respondWithToken($token);
        return $result;
    }

この設定で、ログインに3回失敗すると1分間のロックアウトがかかり、429 Too Many Requests レスポンスが返ります。
ロックアウト制限中は正しいログイン情報を送ってきてもログインは通しません。

sendLockoutResponse($request) には、「あと何秒で使えるようになるよ」という情報が入っているのですが、
さらにセキュリティを高めるのであれば単純に abort(429) を返せば良いでしょう。

 if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);
            abort(429);
        }

失敗のしきい値などはenvファイルに書いて管理したほうが良いかもしれませんね。
参考になりましたらLGTMボタンお願いします