Laravelのmiddleware authを理解したい (5.5)

**6/21 13:22 最終更新完了です。**

Laravelの認証の仕組みを利用することは、公式ドキュメントや色々な記事を読むことで可能になりましたが、
「じゃあ実際どういう仕組みで認証してるんだろう」という疑問にぶち当たり、調べて見ました。

[Laravelのmiddleware authを理解したい](https://qiita.com/washio12/items/59f5cde23b4205973c6b)
こちらの記事のLaravel5.5版です。構成も丸パクリさせていただいています。

# 環境
Laravel 5.5
PHP 7.0
MacOSX 10.13

# 調査
```php artisan make:auth```
で自動生成されたコードによれば、認証が必要なものはMiddlewareで定義する。

```php
class HomeController extends Controller
{
    //コンストラクタ
    public function __construct()
    {
        $this->middleware('auth');
    }
```

・Http/Kernel.phpにて「auth」というaliasを登録している

```php:Http/Kernel.php
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
```

※ 5.5〜 Framework内のクラスを利用するように変更された（らしい）

## \Illuminate\Auth\Middleware\Authenticate でやっていること
- handleが定義されている（[自明ですかね](https://readouble.com/laravel/5.5/ja/middleware.html)）
- [ガードを指定する](https://readouble.com/laravel/5.5/ja/authentication.html#protecting-routes)ことで、認証の方式を変更できる
    - $guards　に指定された文字列が入る（```$this->middleware('auth:web,api');``` のように複数指定が可能なので配列）
- $guardsが指定されていないときは、\Illuminate\Auth\AuthManager\authenticate() を呼ぶ →　本資料はこちらについてだけ理解を進める
    - 実装は ```interface Illuminate\Contracts\Auth\Factory``` だが、実際にDIコンテナに登録されているのは上のクラス（\Illuminate\Foundation\Application\registerCoreContainerAliases()）

## \Illuminate\Auth\AuthManager\authenticate() が呼ばれるまで

- \Illuminate\Auth\AuthManager\authenticate() はコード上存在しないです。

```php:\Illuminate\Auth\AuthManager.php
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
```

### $this->guard() でオブジェクトが特定されるまで

```php:Illuminate\Auth\AuthManager.php
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }
```

- $this->resolve() で解決している模様
- $nameは、"web"(デフォルト: auth.default.web)

```php:Illuminate\Auth\AuthManager.php
    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
    }
```

- $config['driver'] は、config/auth.php で定義しているdriverが読み込まれる。
    - デフォルトのままだと'session'
- ```$driverMethod = 'createSessionDriver'``` で話を進める

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

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

        // When using the remember me functionality of the authentication services we
        // will need to be set the encryption instance of the guard, which allows
        // secure, encrypted cookie values to get generated for those cookies.
        if (method_exists($guard, 'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        if (method_exists($guard, 'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard, 'setRequest')) {
            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
        }

        return $guard;
    }
```

- **SessionGuard が、guardクラス**

### createUserProviderでやっていること

↓　[5.2解説より](https://qiita.com/washio12/items/59f5cde23b4205973c6b#createuserprovider%E3%81%8C%E3%82%84%E3%81%A3%E3%81%A6%E3%82%8B%E3%81%93%E3%81%A8) (同じ模様)

- configのauth.providers.{$provider}の値 → auth.providers.usersの値を取得する
    - デフォルト：auth.providers.users = ['driver'=>'eloquent','model'=>'App\User']
- 「何を使って、どこからUser情報を取得するか」というのを取得する。
- デフォルトでは、「eloquentを使って、App\Userモデルの情報をとる」ということ

本稿の追記

- Laravelで用意されているUserProviderは以下
    - auth.providers.users.driver = 'eloquent'の場合は、Illuminate\Auth\EloquentUserProvider
    - 'database'の場合は、Illuminate\Auth\DatabaseUserProvider
- UserProviderの主なメソッドは以下
    - retrieveById()：IDで認証する
    - retrieveByToken()：token(remember me)で認証する
    - updateRememberToken()：token(remember me)を更新する
    - retrieveByCredentials()：credentialを更新する
    - validateCredentials()：credentialを利用してユーザを検証する

## new SessionGuard()でやっていること

```php:Illuminate\Auth\SessionGuard.php
    public function __construct($name,
                                UserProvider $provider,
                                Session $session,
                                Request $request = null)
    {
        $this->name = $name;
        $this->session = $session;
        $this->request = $request;
        $this->provider = $provider;
    }
```

- セッションやリクエストのセットを行っている
- [5.2と同じ模様](https://qiita.com/washio12/items/59f5cde23b4205973c6b#new-sessionguard%E3%81%A7%E3%82%84%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%93%E3%81%A8)

## SessionGuard::authenticate() でやっていること

- authenticate()　メソッドは Illuminate\Auth\GuardHelper に存在(use で読み込まれている)

```php:GuardHelper.php
    public function authenticate()
    {
        if (! is_null($user = $this->user())) {
            return $user;
        }

        throw new AuthenticationException;
    }
```

## user() がやっていること

- GuardHelperの$this->userは、SessionGuard に実装されている（戻った）
- $this->loggedOut = true だったらnullを返す
    - logout() で trueがセットされる。ログアウトしたけどSessionが残っているのを防ぐ模様
- $this->user が存在していれば、それを返す
    - DBアクセスを減らす目的
    - これに該当することってあるんですかね？
-  SessionからIDを取得
    - Sessionについては別記事でまとめる

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

- IDが取れたら、UserProviderのretriveByIdで、Userオブジェクトを取得する
    - 取得できたら認証完了のイベント通知を行う
- ```$recaller = $this->recaller()``` で、Recallerオブジェクト生成
    - "remember me" Cookieの情報を保持する
    - 情報がない場合はnull
- ここまでで```$this->user``` がとれていなくて、```$recaller``` が存在している場合、Recallerから```$this->user```の取得を試みる
- 最後にログインユーザが取れていれば```$this->user``` 、取れていなければnull（ログインなし）を返す

---
以上です。