0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Laravel12】ログイン認証だけで満足しない!'auth'ミドルウェアでゲストユーザの遷移先を制御しよう

Posted at

はじめに

Laravelのバージョン

本記事では、Laravel12での開発を前提としています。
構造的にはLaravel11と同じはずで、11.xか12.xであれば動くとは思うのですが、バージョンが異なる場合は動作保証ができません。バージョンが異なる環境の場合は、あくまでも参考程度に捉えてください。

ログイン処理の実装

本記事では、ログイン処理ができるようになった、その先のことについて触れます。つまり、ログインができる状態でないと、あんまり意味がありません。

さらに、複数のログイン認証モデルがあることで、この記事は本領を発揮します。例えば、「顧客ユーザと管理者ユーザでそれぞれログインできるようにしたい!」という場合です。

認証モデルが1つの場合は、デフォルトで用意されている機能で十分です

また、認証モデルを新たに作成したい場合は、以下の記事でその方法を説明しております。「自作モデルでのログイン認証実装方法が分からない」という方は、そちらを先にお読みください。そのうえで、自作モデルでのログイン機能実装を先に済ませることをオススメします

本記事における仮のモデル等

本記事では、以下のモデル・ガードで説明を進めます。

  • Customerモデル:顧客モデル。Customerと出されたらコレ
  • customersガード:顧客用ガード。customersと出されたらコレ
  • Adminモデル:管理者モデル。Adminと出されたらコレ
  • adminsガード:管理者用ガード。adminsと出されたらコレ

モデルは、DBなどに登録されるデータとか実体。
ガードとは「そのモデルでログインしてるか?」を確認するための受付とか門。
本記事においては、これくらいの大雑把な認識で良いと思います。本記事をお読みになるのであれば、恐らく分かっていると思いますが

目的

デフォルトのauthミドルウェアエイリアスを使いつつ、ゲストユーザの遷移先をガード毎に変えること

本記事では、「ログイン用に作成したガード」に応じて、ゲストユーザが保護されたルートへアクセスしたときの遷移先を変える方法を説明します。特に、ルート保護の時に使っていた'auth'というエイリアスを引き続き使っていけるようにしていきます。

実装

ルーティング

各モデル向けに名前を付けながらルーティングしていきます。ここでの命名規則は以下の通りです。

  • Customer関連ルート:customer.~
  • Admin関連ルート:admin.~

特にlogin名前付きルートなど名前の被りやすいルートに関しては、命名規則などを使ってユニークなものにしてください

web.php
/* customer向けのルーティング */
Route::get('/customer/login', function () {
    return view('customer.login');
})name('customer.login');

Route::post('/customer/login', [CustomerLoginController::class, 'authenticate']);

Route::get('/customer/dashboard', function () {
    return view('customer.dashboard');
})->middleware('auth:customer')->name('customer.dashboard');

/* admin向けのルーティング */
Route::get('/admin/login', function () {
    return view('admin.login');
})->name('admin.login');

Route::post('/admin/login', [AdminLoginController::class, 'authenticate']);

Route::get('/admin/dashboard', function () {
    return view('admin.dashboard');
})->middleware('auth:admin')->name('admin.dashboard');

ミドルウェアのクラス拡張

指針

Illuminate\Auth\Middleware\Authenticateを拡張するクラスを作成します
さらに、そのクラスの中で、redirectToメソッドをオーバーライドします

ミドルウェア実装

まずは、ミドルウェアファイルを作成します。ミドルウェア名は何でも構いません。

bash
php artisan make:middleware Authenticate

redirectToメソッドをオーバーライドすることを主として、クラスを作成します。
指針としては、入力されているガードに対してif文やswitch文などで条件分岐をかけることになります。

App\Http\Middleware\Authenticate.php
use Illuminate\Auth\Middleware\Authenticate as Middleware; //追記
use Illuminate\Support\Arr; //追記

class Authenticate extends Middleware
{
    protected $guards;

    public function hundle($request, Closure $next, ...$guards)
    {
        $this->guards = $guards;

        return parent::hundle($request, $next, ...$guards);
    }

    protected function redirectTo(Request $request)
    {
        if($request->expectsJson()) return null;
        switch(Arr::last($this->guards)){
            case "admin":
                return route("admin.login");
                break;
            case "customer":
                return route("customer.login");
                break;
            default:
                return route("customer.login");
                break
        }
    }
}

ミドルウェアエイリアスの上書き

ルート保護に使う'auth:~'というエイリアスが、拡張したAuthenticateミドルウェアとなるように設定を上書きします。
この設定は、bootstrap\app.phpで行います。
->Middleware(function (Middleware $middleware) { ... }のコールバック関数に記述をしていきます。

bootstrap\app.php
use App\Http\Middleware\Authenticate; // インポートとして追記

->Middleware(function (Middleware $middleware) {
    $middleware->alias([
        "auth" => Authenticate::class
    ]);
})

補足説明

'auth'エイリアスによるルート保護

  • middleware('auth'):デフォルトのガードを利用する
  • middleware('auth:~'):「~」というガードを利用する
  • middleware(['auth:~', 'auth:=']):「~」のガードを検証し、通過すれば「=」のガードを検証する(=どちらも通過しなければならない)
  • middleware('auth:~,='):「~」か「=」のどちらかのガードを通るか、その順で検証する(=どちらかが通過すれば良い)

後半2つの書き方では挙動が異なります。正直、「どちらかを通過していれば良い」という要件をいつ使うのかは、開発初心者の私には見当がつきません。コメント等で教えていただけますと嬉しいです。
どちらにしても、先に記述されたガードが、先に検証されます。どのガードを優先するのか、どのガードで検証落ちしたらどの画面へ遷移させるのか、等の要件を整えてから実装しましょう。

redirectToメソッドによる遷移先の選択

好きなように条件やその分岐、遷移先を記述しましょう。
今回はArr::lastメソッドを用いて「どのガードに対しても検証落ちしたら、その検証群のうち最後に書かれたガード」を取り出して、条件分岐させました。ここで、「検証群」とは、'auth:a,b'のように「複数のガードのうち、どれかを通るか検証する」ものとします。

  • 動作例
    • middleware('auth:a,b')で、abどちらも検証落ち→bのログインページへ
    • middleware(['auth:a', 'auth:b'])で、どちらも検証落ち→aのログインページへ

例えば、「検証群のうち最も最初に書かれた(優先度の高い)ガード」を取り出したい場合はArr::firstメソッドを利用すれば良いです。

また、絶対に配列要素を参照してリダイレクトしなければいけないという制約もありません。容赦なくルートページや404ページに飛ばすとかしても良いです。

ミドルウェアのデフォルトエイリアスの上書き

Laravel12では、いくつかのミドルウェアのエイリアスが、デフォルトで設定されています。

「これを上書きしてエラー吐かないのか」という話ですが、吐きません。動きます。

内部処理を見てみると、なんやかんやあって、以下のコードでエイリアスが格納されています。

public function getMiddlewareAliases()
{
    return array_merge($this->defaultAliases(), $this->customAliases);
}

defaultAliases()では、デフォルトエイリアスが記述された連想配列を読み込みます。customAliasesには、bootstrap\app.phpで連想配列として記述した連想配列が入っています。
これらをマージして、ミドルウェアエイリアスが形成されるわけですね。

では、array_mergeメソッドの挙動について、PHP公式ドキュメントを見てみましょう。

入力配列が同じキー文字列を有していた場合、そのキーに関する後に指定された値が、 前の値を上書きします。

つまり、今回のように「'auth'エイリアスを、別のミドルウェアとして同名で上書きする」みたいなことが許容されるのです。
もし必要があれば、メールアドレス認証用のエイリアス'verify'とかも、同じエイリアス名で上書きできる、ということです。

おまけ:ガード毎にミドルウェアを作る

今回は、主としてredirectToメソッドのみを弄って「ガード毎にゲストユーザの遷移先を変える」という機能を実装しました。
これは、1つのミドルウェアを使って複数ガードのゲスト遷移先を制御するという設計に基づいています。認証周りを全部'auth:~'で統一するには、確かにベストプラクティスかもしれませんね。

ただ、私は先述の通り、「どれかのガードを通ればOKって、どんな設計?」と感じています。ならば、いっそガード毎にミドルウェアを作ったほうが、責務分離がされるのではないかと考えました。

もちろん、これは可能です。指針は以下の通りです。

  • authenticateメソッドをオーバーライド
    ログインしているかの検証を、対象となるガードのみにします。
  • redirectToメソッドをオーバーライド
    ページ遷移先をただ指定します。

最終的に以下のようになります。

SomeGuardIsAuthenticated.php
use Illuminate\Auth\Middleware\Authenticate;

class SomeGuardIsAuthenticated extends Authenticate
{
    public function ($request, $guards)
    {
        if($this->auth->guard('guard')->check()){
            return $this->auth->shouldUse('guard');
        }

        $this->unauthenticated($request, $guards);
    }

    public function redirectTo($request, $guards)
    {
        if($request->expectsJson()) return null;
        return route('guard.login');
    }
}
routes/web.php
Route::get('hoge', function () {...})
->middleware(SomeGuardIsAuthenticated::class); // ミドルウェアの適用

メインで説明していた方法を知ると、わざわざこれをする必要はないように思います。chatGPT君に使い道を聞いてみても、「責務分離がはっきりする分、テストや拡張がしやすい」としか、直接的に受けられる利点はなさそうです。

特定のガードに関して、著しく複雑な検証を行うとか、多要素を用いて検証を行うとかをするのであれば、こちらの方がすっきりすると思います。
要件や実装規模に合わせて、デフォルトエイリアスを引き継ぐか、専用ミドルウェアクラスを実装するかを検討しましょう。

最後に

本記事では、ミドルウェアを拡張してゲストのリダイレクト先を制御する方法を説明しました。

ここら辺のお話は、chatGPT君に聞くと微妙に信じきれないレベルで返ってきます。Kernel.phpは11.xで廃止されているし、ミドルウェアの設定方法も若干変わっています。
「調べても生成AIに聞いてもできなかった」という方の助けになれれば幸いです。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?