laravel authの処理を追う

laravelのauth処理を追う。

HomeControllerのコンストラクタでauth middlewareが指定されている想定。

```lang:HomeController.php
class HomeController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

```

auth middlewareの実態はkernelに記載されているはず。
見てみるとAuthenticate(=認証)クラスが指定されていた。
認証はユーザーが誰であるかを判断する仕組みのこと

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

Authenticateのロジックを見ていく。
middlewareは実行時にhandle関数が呼ばれる。第3引数以降はミドルウェア独自に指定できる引数になる。
Authミドルウェアはguardを引数で渡せるようだ。↓きっとこういう感じで渡すのだろう。複数guardを用意して適宜guardを指定する仕組み。
`$this->middleware('auth:guard名');`

```lang:Authenticate.php
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($guards);

        return $next($request);
    }
```

authenticate関数を見ていく。
guardが空の場合はauthのauthenticate関数を実行している。
guard要素がある場合はauthを使ってguardインスタンスを取得し、check関数を実行している。checkが通ればshouldUseするのか。
いずれも通らなければAuthenticationExceptionが発生してユーザーにエラーが表示される。

```lang:Authenticate.php
    protected function authenticate(array $guards)
    {
        if (empty($guards)) {
            return $this->auth->authenticate();
        }

        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }

        throw new AuthenticationException('Unauthenticated.', $guards);
    }
```

Authenticateのauthはコンストラクタで渡される。\Illuminate\Contracts\Auth\Factoryが渡されるようだ。
middlewareはサービスコンテナによってインスタンス化されるため、これの実態が何かはパッとは分からない。

```
    public function __construct(Auth $auth)
    {
        $this->auth = $auth;
    }
```

サービスコンテナにinstanceをbindしている箇所は限られている。以下のどこかに定義があると思う。
・confog/app.php
・ServiceProvider
・Kernelのbootstrap

config/app.phpのprovidersで指定されている`Illuminate\Auth\AuthServiceProvider::class`にbind処理が書いてあった。
authの実態は`AuthManager`だ。こいつがguardのインスタンスを返したり認証チェックを行ったりする。

```
    protected function registerAuthenticator()
    {
        $this->app->singleton('auth', function ($app) {
            // Once the authentication service has actually been requested by the developer
            // we will set a variable in the application indicating such. This helps us
            // know that we need to set any queued cookies in the after event later.
            $app['auth.loaded'] = true;

            return new AuthManager($app);
        });

        $this->app->singleton('auth.driver', function ($app) {
            return $app['auth']->guard();
        });
    }
```


AuthManagerを見てみる。
先ほど使用されたguard関数, shouldUse関数, authenticate関数を追う。

まずはguard関数。引数でもらったguard名もしくはdefault名を使ってGuardインスタンスを返すようだ。
今までguardと読んでいたものはここではdriverと言われるのだろうか。

```
    /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

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

shouldUse関数をみる。デフォルトのdriverを指定できるようにする関数のようだ。
routeやcontrollerで複数guardを指定した時にどれをデフォルトdriverにするか決めるのに指定していたのか。

```
    /**
     * Set the default guard driver the factory should serve.
     *
     * @param  string  $name
     * @return void
     */
    public function shouldUse($name)
    {
        $name = $name ?: $this->getDefaultDriver();

        $this->setDefaultDriver($name);

        $this->userResolver = function ($name = null) {
            return $this->guard($name)->user();
        };
    }
```

authenticate関数を追う。が、AuthManagerにはこの関数は存在しない。
__callマジックメソッドで代わりにguardの関数を実行しているようだ。

```
    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
```

もう少しguardの実態を追っていく。
authミドルウェアで使用されるguardはrouteやcontrollerでauthを指定する時に一緒に指定できる。
HomeControllerではguard未指定なのでdefaultが使用されるようだ。

```lang:HomeController.php
    public function __construct()
    {
        $this->middleware('auth');
    }
```

default guardはgetDefaultDriver関数で決められる。
config/auth.defaults.guardに定義されているようだ。見てみるとwebというのが指定されている。
web・・・？

```lang:AuthManager.php
    public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }
```

```lang:config/auth.php
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
```

guardの実態はAuthManagerのresolve関数で得られるようだ。
defaultで`web`が指定されていたので$name = 'web'になる。
configを取得し、customCreatorならcallCustomCreatorを実行、そうじゃないならcreateOOODriver関数を文字列から作成して実行するようだ。

```
    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 driver [{$config['driver']}] for guard [{$name}] is not defined.");
    }

```

getConfigを追う。config/auth.guards.webに指定されているようだ。
webのguardではsessionとproviderが設定されていた。

```
    /**
     * Get the guard configuration.
     *
     * @param  string  $name
     * @return array
     */
    protected function getConfig($name)
    {
        return $this->app['config']["auth.guards.{$name}"];
    }
```

```
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],
```

つまりwebのguardでは`createSessionDriver`という関数名を作成し、sessionベースの認証を行うguardインスタンスを作成するということなのか。

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

だんだんguardの実態が分かってきた。createSessionDriverを追っていく。
まずcreateUserProviderでprovider?を作成するようだ。webのconfig['proivder']は`users`が指定されていた。なんやかんやあって`EloquentUserProvider.php`が作成され、それを元に`SessionGuard.php`が作成される。
長かったけどHomeControllerで使用されるguardは`SessionGuard`インスタンスのようだ。なるほど。

```
    /**
     * Create a session based authentication guard.
     *
     * @param  string  $name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    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;
    }
```


authミドルウェアの実装に戻るとguardのauthenticate関数が呼ばれていた。SessionGuardのauthenticate関数を追う。
見てみるとSessionGuardでuseしているGuardHelperで定義され、内部でSessionGuardのuser関数を読んでいるようだ。

```lang:Authenticate.php
    protected function authenticate(array $guards)
    {
        if (empty($guards)) {
            return $this->auth->authenticate(); //実際はguardのauthenticate関数呼び出し
        }
```

```lang:SessionGuard.php
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    use GuardHelpers, Macroable;

```

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

        throw new AuthenticationException;
    }
```

SessionGuardのuser関数を追う。
ここでは`現在認証済みのuser`を返すようだ。sessionからuserを取得する。

```
    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {
        if ($this->loggedOut) {
            return;
        }

        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (! is_null($this->user)) {
            return $this->user;
        }

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

        // First we will try to load the user using the identifier in the session if
        // one exists. Otherwise we will check for a "remember me" cookie in this
        // request, and if one exists, attempt to retrieve the user using that.
        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

        // If the user is null, but we decrypt a "recaller" cookie we can attempt to
        // pull the user data on that cookie which serves as a remember cookie on
        // the application. Once we have a user we can return it to the caller.
        $recaller = $this->recaller();

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

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

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

        return $this->user;
    }
```

あとはauthミドルウェアに処理が戻り、sessionGuardでreturnしたが使われることもなく、$nextが実行され次のmiddlewareの処理が進む。

```lang:Authenticate.php
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($guards);

        return $next($request);
    }
```

あれ・・・認証されていないとリダイレクトされるはずだけどどこでやってるんだろう。
後ほど調べる。