22
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Laravelのカスタムガードでステートフルな認証を実現する

Last updated at Posted at 2021-03-29

前置き

Laravelの認証ではIlluminate\Contracts\Auth\Guardを実装することで、自前の認証ロジックを組み込むことができます。

https://readouble.com/laravel/8.x/ja/authentication.html#adding-custom-guards

例えば、このようなGuardを作成すると、パラメータにidというキーの値が含まれている時、そのIDのUserとして認証をさせることができます。
この認証ロジックを工夫することで、JWTを使った認証や、その他外部サービスと連携した認証が行えるという訳です。。

app/Auth/MyGuard.php
MyGuard implements Guard
{
    use GuardHelpers;

    private Request $request;

    private UserProvider $userProvider;

    /**
     * MyGuard constructor.
     * @param Request $request
     * @param UserProvider $userProvider
     */
    public function __construct(Request $request, UserProvider $userProvider)
    {
        $this->request = $request;
        $this->userProvider = $userProvider;
    }

    public function user()
    {
        $id = $this->request->get('id');
        if ($id) {
            $this->user = $this->userProvider->retrieveByCredentials(['id' => $id]);
        }
        return $this->user;
    }

    public function validate(array $credentials = []): bool
    {
        return false;
    }

この自前のGuardをサービスプロバイダに登録します。

app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
...
    public function boot()
    {
        Auth::extend('custom', function ($app, $name, array $config) {
            return new MyGuard(request(), Auth::createUserProvider($config['provider']));
        });
    }
}

そして、auth.phpを編集し登録したサービスをアプリケーションで使用するように設定します。

config/auth.php
    'guards' => [
        'web' => [
            'driver' => 'custom',
            'provider' => 'users',
        ],

最後に、web.phpに以下のようなルートを追加し、http://localhost/users?id=1にアクセスすると、DBにID1のユーザーが登録されている時、そのユーザーのnameが表示されます。

routes/web.php
Route::get('/users', function () {
    /** @var User $user */
    $user = Auth::user();
    return $user->name ?? 'not logged in';
});

本題

Laravelの認証に自前のロジックを組み込む方法はわかりました。しかし、上で作成したGuardはステートレスな物になっており、認証をするためには毎回id=1のパラメータを付与する必要があります。

一度id=1で認証したら、二度目以降はパラメータなしでも認証できるようにするにはどうしたら良いのでしょうか?

LaravelのドキュメントにはAuth::login($user, $rememberMe)というメソッドを実行し、$rememberMetrueにすることで次回から自動ログインできる、と書いてあります。

https://readouble.com/laravel/8.x/ja/authentication.html#authenticate-a-user-instance

なので、以下のようにユーザーを取得したタイミングで、このメソッドを呼び出せば良さそうです。

    public function user()
    {
        $id = $this->request->get('id');
        if ($id) {
            $this->user = $this->userProvider->retrieveByCredentials(['id' => $id]);
            Auth::login($this->user, true)
        }
        return $this->user;
    }

が、実はこれでは動きません。このAuthファサードのlogin()メソッドは実はアプリケーションで使用されているGuard(今回はCustomGuard)のlogin()メソッドのエイリアスになっているので、CustomGuard自体にlogin()メソッドを実装する必要があるのです。

もう少し正確な話をすると「ステートフルな認証を行う場合、アプリケーションで使用するGuardはIlluminate\Contracts\Auth\StatefulGuardを実装する必要があり、login()メソッドはその中で実装しなければいけないメソッドの一つ」ということです。

StatefulGuardlogin()メソッドの他にこれらのメソッドを実装する必要があります。

  • attempt()
  • once()
  • login()
  • loginUsingId()
  • onceUsingId()
  • viaRemember()
  • logout()

これらのメソッドを生真面目に実装しても良いのですが、認証状態を継続したい、という要件だけならSessionGuardを継承してしまうのが早いです。

まず親クラスのSessionGuardのuser()を呼び出し、ステートフルな認証情報からユーザーが取得できたら、それを返します。見つからなかった場合は、自前の認証ロジックを呼び出し、SessionGuardlogin()メソッド(Auth::login()と同じ意味)を呼び出し、ログイン状態を記憶します。

class MyGuard extends SessionGuard
{
    public function __construct($name, UserProvider $provider, Session $session, Request $request = null)
    {
        parent::__construct($name, $provider, $session, $request);
    }

    public function user()
    {
        $this->user = parent::user();

        if ($this->user) {
            return $this->user;
        }

        $id = $this->request->get('id');
        if ($id) {
            $this->user = $this->provider->retrieveByCredentials(['id' => $id]);
            $this->login($this->user);
        }
        
        return $this->user;
    }
}

プロバイダーへの登録はこのようになります。

class AppServiceProvider extends ServiceProvider
{
...
    public function boot()
    {
        Auth::extend('custom', function ($app, $name, array $config) {
            $session = $this->app->make(Session::class);
            return new MyGuard($name, Auth::createUserProvider($config['provider']), $session, request());
        });
    }
}

以上!

22
16
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
22
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?