Laravel の Guard（認証） って実際何をやっているのじゃ？

認証周りが絡む特殊な実装をしようとしたときに、色々と調べてみたのでせっかくだしまとめます☺️

Guard の使い方等は 「認証」ドキュメントをご参考ください。
[Laravel 5.8 認証]
(https://readouble.com/laravel/5.8/ja/authentication.html)

# Guardってどうやって使うんだったっけ？（おさらい）

```config/auth.php
    'guards' => [
        'guard-name' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],
```

こんな風に `guards` 設定しておいて、

```route/web.php
Route::get('profile', function() {
})->middleware('auth:guard-name');
```

ルートで middleware を呼ぶと、指定した Guard で **認証されたユーザーだけにアクセス許可** します🔑

# Guard クラスはどうやって呼び出すのじゃ？
`'driver' => 'session'` のように、 **session** / **token** 等の Guardクラス の driver を指定します。
独自の **カスタム Guard** やパッケージで追加した Guard を指定することも可能です。

## driver って何なのじゃ？
Guard に付けられた名前ぐらいに思っておけば大丈夫そうです。
カスタムGuard の場合は、 Guard インスタンスを返すクロージャでした！
（詳しくは↓ [カスタム Guard はどうやって登録されるのじゃ？](#カスタム-guard-はどうやって登録されるのじゃ) 参照）

## カスタム Guard はどうやって登録されるのじゃ？
カスタム Guard を登録するには、ドライバ名と Guard （を継承・実装した）クラスオブジェクトを返すクロージャを、`Auth::extend()` に渡します。

```AuthServiceProvider.php
    public function boot()
    {
        $this->registerPolicies();

        Auth::extend('jwt', function($app, $name, array $config) {
            return new JwtGuard(Auth::createUserProvider($config['provider']));
        });
    }
```

ここで呼び出されている `Auth::extend()` では、以下のように driver を登録しています。

```AuthManager.php
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this;
    }
```

認証時等に同クラスの `callCustomCreator()` が呼び出され、登録した **Guard インスタンス** を返します。

```AuthManager.php

    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config['driver']]($this->app, $name, $config);
    }
```

## SessionGuard や TokenGuard はどのように呼ばれるのじゃ？
デフォルトで用意されている `session` と `guard` 用には `createSessionDriver()` `createTokenDriver()` が `AuthManager` 内に用意されており、この中で Guard クラスから new していました😃

```AuthManager.php
    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        // 略

        return $guard;
    }
```

# Guardクラス は何をしているのじゃ？
アプリケーションから Guard を呼び出すには、基本的に `Auth` ファサードを使います。
**`Auth` ファサードは、 Guardインスタンスに静的なインターフェイス** を提供しています。
`Auth::login()` は Guardインスタンスの `login()` 、 `Auth::user()` はGuardインスタンスの `user()` を呼び出しています。
ただし `Auth::login()` 等で呼び出すのは `auth.php` でdefault に指定したguardです。
それ以外の guard を呼び出すには `Auth::guard('api')->login()` のように guard 名を指定する必要があります。

## SessionGuard は何をしているのじゃ？
ではここで、使用頻度が高そうな `SessionGuard` を深掘りしてみましょう🙌 
よく使うメソッドをいくつか選んでみましたが実際にはもっとたくさんありますので興味がある方は SessionGuard を読んでみてください☺️

### attempt （ログインを試みる）
```SessionGuard.php
    public function attempt(array $credentials = [], $remember = FALSE)
    {
        $this->fireAttemptEvent($credentials, $remember);

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

        if ($this->hasValidCredentials($user, $credentials))
        {
            $this->login($user, $remember);

            return TRUE;
        }

        $this->fireFailedEvent($user, $credentials);

        return FALSE;
    }
```

イベント発火🔥等しつつ、 credentials をチェックして（ `$this->provider->retrieveByCredentials()` ）正しければログイン処理（ `$this->login()` ）しているようです。
credentials のチェック・ユーザー取得部分は Provider のお仕事なので後述します✍️


### login （ログインする）
⬆️ の `attempt()` からも呼ばれるし、 `Auth::login` で呼ぶこともありますね。

```SessionGuard.php
    public function login(AuthenticatableContract $user, $remember = FALSE)
    {
        $this->updateSession($user->getAuthIdentifier());

        if ($remember)
        {
            $this->ensureRememberTokenIsSet($user);

            $this->queueRecallerCookie($user);
        }

        $this->fireLoginEvent($user, $remember);

        $this->setUser($user);
    }
```

- Sessionの更新
- Remember Me 関連のゴニョゴニョ
- イベント発火
- インスタンスにデータの保持

概ねイメージ通りでした。

### user （ログイン済ユーザーを取得する）
```SessionGuard.php
    public function user()
    {
        if ($this->loggedOut)
        {
            return;
        }

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

        $id = $this->session->get($this->getName());

        if ( ! is_null($id) && $this->user = $this->provider->retrieveById($id))
        {
            $this->fireAuthenticatedEvent($this->user);
        }

        if (is_null($this->user) && ! is_null($recaller = $this->recaller()))
        {
            $this->user = $this->userFromRecaller($recaller);

            if ($this->user)
            {
                $this->updateSession($this->user->getAuthIdentifier());

                $this->fireLoginEvent($this->user, TRUE);
            }
        }

        return $this->user;
    }
```
`$this->user` が既にあればそのユーザーを、ない場合はセッションからユーザーIDを取り出して Provider に渡し、ユーザーオブジェクトを取得・返却しています。

なおセッションには以下のキーで保持されているようです。

```SessionGuard.php
    public function getName()
    {
        return 'login_' . $this->name . '_' . sha1(static::class);
    }
```

Guard クラス（Guard オブジェクト）の仕事がざっくり理解できた気がします！

# Provider って何なのじゃ？
`guard` 設定で呼ばれていた Provider は、以下のように定義します。

```auth.php
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Entities\User::class,
        ],
    ],
```

driver には **eloquent** か **database** 、または `UserProvider` を実装した**カスタム UserProvider** のドライバを指定できます。
アプリケーションで Eloquent を使っている場合は、 `eloquent` を指定することがほとんどでしょうか😃
`eloquent` の場合は **model** を、 `database` の場合は **table** を併せて指定します。

## Provider はどこから呼ばれるのじゃ？
『[SessionGuard は何をしているのじゃ？](#sessionguard-は何をしているのじゃ)』 で書いた通り Guard から `$this->provider->hogehoge()` のようにインスタンスが参照されます。
このインスタンスはどこで作られているのでしょう？

`SessionGuard` の場合、 `AuthManager` の `resolve()` から、上記のメソッドが呼び出されます。

```AuthManager.php
    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app['session.store']);

        // 略
    }
```
この `createUserProvider()` 内で Provider を new して、 Guard のコンストラクタに渡しているようです。
そういえばカスタムGuard の登録時も、 Guard のコンストラクタに `createUserProvider()` を渡していましたね😃

## EloquentUserProvider は何をしているのじゃ？
今度は、使用頻度の高そうな `EloquentUserProvider` の中身を見ていきます🙌

### ログインしようとしているユーザーを取得する
`SessionGuard` の `attempt()` から呼び出されていた `retrieveByCredentials()` を見てみましょう。

```EloquentUserProvider.php
   public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password', $credentials))) {
            return;
        }

        $query = $this->newModelQuery();

        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }
```

ふむふむ。
パスワードを除いた Credentials （メールアドレスやユーザーID）でユーザーを絞り込んでますね。
`newModelQuery()` では `model` で指定された モデル （User等） を new し、その Query オブジェクト返しています。

```EloquentUserProvider.php
    protected function newModelQuery($model = null)
    {
        return is_null($model)
                ? $this->createModel()->newQuery()
                : $model->newQuery();
    }
```

```EloquentUserProvider.php
    public function createModel()
    {
        $class = '\\'.ltrim($this->model, '\\');

        return new $class;
    }
```


### パスワードを検証する
`retrieveByCredentials()` ではパスワードが検索対象から除外されていましたね。
パスワード検証には別途、 `validateCredentials` が用意されています。

```EloquentUserProvider.php
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }
```

### IDからユーザーを取得する
Guard の `user()` 等から呼ばれます。
シンプルな検索ですね😃

```EloquentUserProvider.php
    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->newModelQuery($model)
                    ->where($model->getAuthIdentifierName(), $identifier)
                    ->first();
    }
```

# まとめ
やっと、Laravelの認証機能の全容（の一部）が見えてきました！やったぜ！