Laravelの認証の仕組み

今までLaravelの認証について殆ど意識してなかったが認証周りの設計することになりLaravelの認証の仕組みを改めて確認してみた。

# 認証のベースの考え方
復習も兼ねて図にしてみた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494456/264c1c3e-0017-8f8d-3dfa-11b507e25211.png)

各手順を文章にすると以下の通り。

**---------------------------------------ログイン前---------------------------------------**
①ユーザーはID、パスワードをブラウザに入力しログインボタン押下
②ブラウザはサーバーに入力されたID、パスワードを添えてログインをリクエスト
**③サーバーのアプリケーションはログインID、パスワードが登録されているものかDBに問い合わせる**
④DBはアプリケーションに結果を返す
**⑤登録されていればアプリケーションはセッションIDを発行し、発行したセッションIDに紐づくセッション領域を生成し、ユーザー情報を格納する**
**⑥アプリケーションは発行したセッションIDを「Set-Cookie」ヘッダに入れてクライアントへレスポンス**
⑦ブラウザはレスポンスの「Set-Cookie」ヘッダの値を取り出してCookie領域に格納
**---------------------------------------ログイン後---------------------------------------**
⑧ブラウザはリクエスト時、Cookie領域に格納された値を自動的に「Cookie」ヘッダに付与
**⑨アプリケーションはCookieヘッダに付与されたセッションIDを持つセッション領域を確認し、存在すればそのセッション領域からユーザー情報を取得**
**---------------------------------------ログアウト---------------------------------------**
⑩ブラウザはサーバーにログアウトをリクエスト（Cookie領域に格納された値を自動的に「Cookie」ヘッダに付与）
**⑪サーバーはCookieヘッダに付与されたセッションIDを持つセッション領域を削除**

:::note info
太字の③⑤⑥⑨⑪がアプリケーションの仕事となる。
:::

# Laravelの認証の仕組み

上記アプリケーションの仕事をLaravelがどのように実現しているのか、コードベースで追っていく。

## ③サーバーのアプリケーションはログインID、パスワードが登録されているものかDBに問い合わせる

まず`retreiveByCredentials()`でIDを元にユーザーを取り出す。
ユーザーを取り出せたら`hasValidCredentials()`で取り出したユーザーと入力されたパスワードを比較する。
一致すればログイン。
※厳密にはDBから取り出したパスワードは暗号化されているので、入力されたパスワードも暗号化しチェックしている。

https://github.com/laravel/framework/blob/4de8ef45937af95b3a6b1c354ee434364e07406c/src/Illuminate/Auth/SessionGuard.php#L359-L368

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

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

            return true;
        }
```

何故このようにユーザー取り出し→パスワード一致チェックの2段階としているかというと認証連続失敗時のロックを可能とするため。

## ⑤登録されていればアプリケーションはセッションIDを発行し、発行したセッションIDに紐づくセッション領域を生成し、ユーザー情報を格納する

`$this->session->put()`でセッションにユーザーIDを格納。

https://github.com/laravel/framework/blob/8918a9dda190c3dee436193a9cb0b5672c914e4e/src/Illuminate/Auth/SessionGuard.php#L494-L499

```php
    protected function updateSession($id)
    {
        $this->session->put($this->getName(), $id);

        $this->session->migrate(true);
    }
```

`$this->session->migrate(true)`で古いセッション領域を削除し、新たなセッションIDを発行。
※セッションIDを固定化するとセッションIDを抜き取られたときのリスクが大きい為。

https://github.com/laravel/framework/blob/8918a9dda190c3dee436193a9cb0b5672c914e4e/src/Illuminate/Session/Store.php#L511-L522

```php
    public function migrate($destroy = false)
    {
        if ($destroy) {
            $this->handler->destroy($this->getId());
        }

        $this->setExists(false);

        $this->setId($this->generateSessionId());

        return true;
    }
```

`generateSessionId()`でセッションIDをランダム文字列40文字で発行

https://github.com/laravel/framework/blob/8918a9dda190c3dee436193a9cb0b5672c914e4e/src/Illuminate/Session/Store.php#L592-L595

```php
    protected function generateSessionId()
    {
        return Str::random(40);
    }
```

## ⑥アプリケーションは発行したセッションIDを「Set-Cookie」ヘッダに入れてクライアントへレスポンス

ミドルウェアでセットすることで毎レスポンス行っている。

https://github.com/laravel/framework/blob/3e8f7fa0c40a2802298ebb64c6485a413d2eccfb/src/Illuminate/Session/Middleware/StartSession.php#L217-L226

```php
    protected function addCookieToResponse(Response $response, Session $session)
    {
        if ($this->sessionIsPersistent($config = $this->manager->getSessionConfig())) {
            $response->headers->setCookie(new Cookie(
                $session->getName(), $session->getId(), $this->getCookieExpirationDate(),
                $config['path'], $config['domain'], $config['secure'] ?? false,
                $config['http_only'] ?? true, false, $config['same_site'] ?? null
            ));
        }
    }
```

## ⑨アプリケーションはCookieヘッダに付与されたセッションIDを持つセッション領域を確認し、存在すればそのセッション領域からユーザー情報を取得

ミドルウェアでクッキーからセッションIDを取り出している

https://github.com/laravel/framework/blob/8918a9dda190c3dee436193a9cb0b5672c914e4e/src/Illuminate/Session/Middleware/StartSession.php#L157-L162

```php
    public function getSession(Request $request)
    {
        return tap($this->manager->driver(), function ($session) use ($request) {
            $session->setId($request->cookies->get($session->getName()));
        });
    }
```

## ⑪サーバーはCookieヘッダに付与されたセッションIDを持つセッション領域を削除

https://github.com/laravel/framework/blob/8918a9dda190c3dee436193a9cb0b5672c914e4e/src/Illuminate/Auth/SessionGuard.php#L603

```php
        $this->session->remove($this->getName());
```