Laravelでsessionとcookieの動向を追う~備忘録~

久しぶりに自サービスの開発に着手しようと思い、sourcetreeを開き、中途半端になっていたブランチを見てみると身に覚えがない履歴が、、
ん？俺こんなマイグレーションファイル作成した覚えないんだが、、
なぜか分かりませんが2022/6/22のタイミングに作成したらしい、、
原因がわかる人がいればコメントなりくださると嬉しいです。
マイグレーションファイルファイルを確認する限り、セッションに関する事だという事が見て取れます。
せっかくなのでセッション、クッキーを理解するために備忘録を書こうと思います。
寄り道ばかりでで全然開発が進みませんが、セッションとクッキーはサイトを運営する上で大切な概念なので今のうちマスターしたいと思います。
※参考にしたサイト
https://reffect.co.jp/laravel/laravel-sessions-understand

#そもそもSession,Cookieとは

HTTPはステートレスなためユーザーがあるページを閲覧して次のページに移動しているか分かりません、そのような状態ではシステムにログインしたとしてもログイン状態が管理できないので会員システムのようなアプリケーションやECサイトのように商品をショッピングカートに保存することはできません。

###ステートレス=状態を維持しないという意味

これを解決するために存在するのがSessionとCokieです。クライアントであるブラウザとサーバ(ここではLaravel)間でSession ID(ブラウザではCookie,サーバではセッション情報)を共有し、そのSessionIDを照会することでユーザーを識別することが可能となります。ECサイトであればSessionの中にショッピングカードの情報を保存することでページを移動しても意味を保持することができます。

###phpでのsession利用方法
phpでは<font color="Red">session_start関数</font>を実行することでSession IDを持つCookieが作成されます。
<font color="Red">setcookie関数</font>を使ってブラウザ上にCookieを作成する事も可能です。cookieに保存されている値はスーパーグローバル変数の<font color="Red"> $_COOKIE, </font><font color="Red">$_SERVER</font>を使って取得する事ができます。クッキーはサーバーへアクセスするたびにヘッダーに含まれて送信されます。ブラウザのデベロッパーツールでCookie,ヘッダー情報を見ることで確認できます。

Cookieはセッションだけでなくアクセス解析にも利用する事ができます。javascriptからもCookieにアクセスすることができるのでjavascriptを使ってアクセスのあったブラウザ情報やアクセス頻度を記録し外部の分析用
サーバに送信することが可能です。


※この備忘録ではララベルのインストール完了、環境構築されている事を前提で話を進めます。


###Sessionsテーブルの作成

自分は何かの拍子に作成されていたのですが、念のため記載しておきます。


###Laravel7以降

自分はLaravel8を利用しているので以下を利用します。

```vim
% php artisan session:table
Migration created successfully!
```

###Laravel6の場合
php artisan make:migrationコマンドでsessionsテーブル用のマイグレーションファイルを作成します。


```vim
$ php artisan make:migration create_sessions_table
```

ファイル自体はdatabase/migrationディレクトリの下に存在します。






なぜか作られていたマイグレーションファイル

![スクリーンショット 2021-06-09 2.02.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/18975a73-56fc-3de9-65d5-563683f7d70b.png)

こちらが保存されていたマイグレーションファイルの中身です。


```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateSessionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('sessions', function (Blueprint $table) {
            $table->string('id')->primary();
            $table->foreignId('user_id')->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->text('payload');
            $table->integer('last_activity')->index();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('sessions');
    }
}


```

sessionテーブルは,id,user_id,ip_address,user_agent,payload,last_activityの列で構成されているようです。



###Sessionsテーブルの作成
勝手に作られたマイグレーションファイルで不服ですが、migrateします、、

```
% php artisan migrate
Migrating: 2021_03_07_003023_create_sessions_table
Migrated:  2021_03_07_003023_create_sessions_table 
```


###Session Driverの変更

サーバー側ではSession IDを保存するのですが、サーバーのどこに保存するかをSession Driverを使って設定します。

保存できる場所にはデフォルトではファイルの他にデータベース、Cookie、redisなどさまざまな場所がありますが、今回はせっかくなのでデータベースを利用します。
.envファイルを編集します。


```zhm
SESSION_DRIVER=file
 ↓ 　↓
SESSION_DRIVER=database

```
.envファイルでSESSION_DRIVERの変更を行いましたがSesiionに関する情報はconfig/session.phpファイルで行います。<font color="Red">SESSION_DRIVERの設定値</font>もこのファイルで利用されます。


.envファイルの設定変更を反映させるためにはphp artisan config:clear を実行します。
因みに自分はこの流れをローカル環境のmacで行っております。


```zsh
php artisan config:clear
Configuration cache cleared!

```

###セッションとクッキー

###セッションとクッキーを目で確認する
セッションとクッキーを確認するために「Awesome Cookie Manager」という拡張機能を使いました。
標準のCokie機能でも確認はできるのでどちらでもいいと思います。

因みに「Awesome Cookie Manager」のUIは以下となっております。
今回は「s_p」という自分が開発したサイトを見ていきたいと思います。
「XSRF-TOKEN」と「laravel_session」が確認する項目となっております。
Valueには文字列が入り、Expiration Dateには日時が記載されております。セッションとクッキーを理解する上でこの二つが重要となるのかーと感じます。

![スクリーンショット 2021-06-09 23.56.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/71e02169-3be9-e210-4de8-dfb7c0d5db01.png)


##laravel_sessionのステータス
![スクリーンショット 2021-06-10 0.01.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/c196c968-f1b5-3bc0-2062-d56da5d27ed8.png)


##XSRF-TOKENのステータス
![スクリーンショット 2021-06-10 0.02.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/ce141a89-3d20-bb4b-7815-809cc4dc8b21.png)



GoogleデベロッパーツールのApplicationでも確認する事ができます。

![スクリーンショット 2021-06-10 0.06.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/a126ade6-c046-70b7-524a-5012dd63c438.png)

###Cookieの名前

デフォルトではcookieの名前がlaravel_sessionとなっていますが、これは固定の名前ではなく、session.phpファイルのcookieのパラメータで変更可能です。

```php
    'cookie' => env(
        'SESSION_COOKIE',
        Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
    ),
```
試しに「s_p_session」に変えてみます。



```php
   'cookie' => env(
        'SESSION_COOKIE',
        Str::slug(env('APP_NAME', 's_p'), '_').'_session'
    ),
```

と変更するもクライアント側laravel_sessionのままです、、
なぜかは分かりませんがこのまま進めます。原因が分かり次第修正したいと思います。

###テーブルのSessionを確認
サーバー側で確認してみます。自分はphpMyAdminで確認しました。
phpMyAdminについては別の機会に言及していきたいと思います。
簡単に説明させていただくと、自身が作成したデータベースのステータスやパラメータをブラウザ上で確認できるツールです！（かなり簡単ですがw）


![スクリーンショット 2021-06-10 0.56.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/a14daaad-11bc-86ba-350f-ab608577f86d.png)


テーブルにはマイグレーションファイルで設定されているid,user_id,ip_address,user_agent,payload,last_actitityが確認できます。

SessionとCookieで同じIDを保持すると説明しましたが、sessionsテーブルのidに対応する値を持つものはCookie側にはありません。

その理由はCookieが持つIDが暗号化されているためです。Cookieの暗号化はLaravel middlewareで行われています。

###middlewaremとは
middleware(ミドルウェア)とはブラウザからLaravelにアクセスした際に実行されるものです。開発者の意向によって柔軟に利用できるのがかなり便利だと思います。

分かりやすい例ですと、アクセスしてくるユーザーが認証済みかどうかSession,Cokieを使ってチェックします。チェックが完了すればそのまま処理を継続されます。
未完了の場合はログイン画面にリダイレクトします。
Laravelですと、そこらへんの設定が意識せずとも開発できてしまうため楽に実装できる反面、深刻なエラーが起きた際に知識がないので修正できないといった事態に陥る可能性があるかもしれません。


###middlewareの中身

ここから少し難しくなりますし、自分も理解していないところもあるのでできるだけ分かりやすく記載していきたいと思います。
各層がどのような処理を行うかはapp¥Http¥Kernel.phpのファイルに記述されています。
3つの変数($middleware, $middlewareGroups, $routeMiddleware)に分かれています。
アクセス元がブラウザであろうとAPIであろうと必ず実行されるのが最初に記述されている$middlewareです。


```php
protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

```

配列に入ったClass一つ一つがmiddlewareで各クラスファイルがRequestに対して独立処理を行い、処理が終わると次のmiddlewareにRequestを渡します。Requestには$middlewareに登録された処理が全て実行されます。
(ホテルとかでいう会員ステータスによってサービスが変わる的なアレですかね、、)
各middlewareは独立されているため、新たに追加する事も可能ですし、削除する事も可能なようです。(自己責任でお願いします。)
追加したい場合は$middleware配列の中にClassを追加、削除したい場合は配列を削除するといった具合です。middlewareの作成はphp aritisan make:middlewareコマンドで行うことができます。


$middlewareGroupsはweb,apiに分かれてグループ化されています。どちらも$routeMiddlewareに登録されているmiddlewareのauth(認証)に関連しておりauthを利用した場合はweb,apiのどちらからを利用することになります。web,apiはそれぞれweb,gurard,api guardとも呼ばれ、web gurardはブラウザからのアクセスに対しての認証処理を行うために行います。SessionやCokieに関する処理はwebのmiddlewareに含まれます。

```php
protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];


```

最後の$routeMiddlewareはグローバルミドルウェアの$middkewareのように全てのRequestに対して実行されるわけではなく、ルーティング(web.php)に対して個別に適用することができるものです。
下記の例では$routeMiddlewareのキーであるauthを指定し、Tokenに対する認証処理を行うためにapiで設定しています。Tokenを使ってLaravelにアクセスがあるとauth
とapiのmiddlewareを組み合わせて認証処理が行われます。

```php
Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});
```

###cookieの作成
ブラウザでLaravelにアクセスするとcokieが自動で作成されます。
ではどのタイミングで作成されるのでしょうか?

cookieの作成はLaravelから戻されるResponseヘッダーの中のSet-Cookieの値をブラウザが受け取ることができます。

LaravelのSessionIDを保存するCookie(デフォルトではlaravel_session,自分はs_psessionに変更しました。)
の作成処理はmiddlewareのStartSessionの中で行われています。作成の詳細は\Illuminate\Session\Middleware\StartSessionのaddCookieToResponseメソッド内で確認できます。

```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
            ));
        }
    }

```
``dump($response)``して値を確認してみました。何かと複雑な文字列が並んでますが、XSRF=TOKENとlaravel_sessionが確認できますね。
\Illuminate\Session\Middleware\StartSessionのaddCookieToResponseメソッドでcookieが作成されている事が分かります。

![スクリーンショット 2021-06-10 19.40.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/3d09df92-0e5b-1881-a11c-a3e57137f736.png)


###cookieの暗号化を解除。
cookieの暗号化を解除することでcookieとsessionで同じSession IDを持つことができます。
middlewareに設定されている処理はclass毎に独立しているため追加、削除することができます。

文字通り\App\Http\Middleware\EncryptCookies::class,で暗号化されているのでこれをコメントアウトします

```php
protected $middlewareGroups = [
        'web' => [
            //\App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

```

どちらも値が短くなっています。




laravel_session
![スクリーンショット 2021-06-10 19.51.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/1996f456-dbef-f214-3508-aa20fbfed258.png)

XSRF-TOKEN
![スクリーンショット 2021-06-10 19.52.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/8c378f0e-4dc3-3bec-1575-5dcc383e01d2.png)




Session tableを確認します。少し分かりづらいですが、idカラムに「uo~」が保存されている事が分かります。

![スクリーンショット 2021-06-10 19.57.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/af3f60fd-ff24-2786-6111-4362fb7c2cec.png)


###last_activityとExpiration

ユーザーを作成するためにResisterリンクを押下してユーザー登録画面に遷移します。
Session情報のlast_activiiy列を確認するとcookieを暗号した時よりも数字が増えます。少し置いてブラウザをリロードすると、さらに数字が増えます。

![スクリーンショット 2021-06-10 20.10.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/80475f02-0dd4-75cd-8280-db3b3a9522b0.png)

この数字はunixタイムスタンプと言われる数字で変換を行うと実行した日付に戻すことができます。last_activityはページにアクセスする毎に更新されます。

Cookie側にも時間に関するExpirationDate(有効期限)を持っています。この値は時間がわかりやすいですね。

laravel_session
![スクリーンショット 2021-06-10 20.15.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/444732bc-9ef6-59bf-607f-9147f2af7c6f.png)

XSRF-TOKEN
![スクリーンショット 2021-06-10 20.15.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/48963024-e1f1-e690-c4da-5efa9416461f.png)

※画像の大きさが違くてすみません、雑な性格なもので、、


画像を見て察しがつくかもしれませんが、つまりユーザーがアクセスしてから2時間の値を表現している事が分かります。
自分がアクセスした時間が20:08なのでセッションの有効期限は22:08となります。簡単ですね。
デフォルトでは2時間に設定されているようです。
これは勿論変更する事が可能です。
ララベルは大変便利で簡単に設定する事ができます。
config/Session.phpファイル内で設定する事ができます。

```php
 'lifetime' => env('SESSION_LIFETIME', 120),
```
4時間に変更したい場合は120→240にするだけです。

.envファイルの設定を反映させるためには ``php artisan config:clear``を実行させる事によって適用させることができます。

###ユーザーのログインによる変化

ユーザーがログインしたらどのようにSessionの情報が変化するか確認してみます。
sessionsテーブルのuser_idカラムを確認しますと、user_idが表示されている事が確認できます。
このIDはログインを行なったユーザーのIDです。

![スクリーンショット 2021-06-10 20.33.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/6de999a2-c3c3-a0c9-a755-6082972ed4ab.png)

ログインを行うことでSessionのuser_idがログインしたユーザのIDに設定される事が分かります。このIDによりどのユーザーの認証が完了しているのかをSession情報から確認する事ができます。

###Sessionsテーブルのpayload
payloadでは文字をエンコードしています。ここでは深く言及しませんが、日本語などをコンピュータがわかるように文字変換するととりあえず覚えておけば良いかと、、間違ってたらごめんなさい。

因みにpayloadとはこの記事がわかりやすいと思います。
https://www.weblio.jp/content/%E3%83%9A%E3%82%A4%E3%83%AD%E3%83%BC%E3%83%89%E3%83%87%E3%83%BC%E3%82%BF

ユーザーが入力したデータなども登録できるようです。
Sessionの処理はmiddlewareのStartSession.phpを使って行われているのでその処理をコードで追います。kernel.phpにてStartSession.phpが使われています。

```php
protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,←これ
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],



```

SessionStart.phpのコードを確認していくとSessionの最後でsaveSessionメソッドが実行されます。saveSessionメソッドはどのDriverを利用するかによってSession
の保存処理は異なりますが、上記でdatabasedriverに設定したのでDatebaseSessionHandler.phpのwriteメソッドでSessionテーブルの書き込みが行われるようです。FileSession.phpなどがあるかは未確認です、、
そのような報告をして頂ける賢人がいてくれると助かります。

一連の流れとしては
1.Karnel.phpが呼び出される
2.Karnel.php内のStartSessionclassが呼び出される
3.StartSessionClass内の処理でsaveSessionメソッドが実行され、driver()を呼び出す
4.store.phpがDatabasesSessionHandler.phpを呼び出す。
5.sessionsテーブルに値が保存される

上記のような流れでしょうか。かなりざっくりではありますが、、


因みに以下がSessionテーブルに書き込みを実行するDataBasessessionhandler.phpのwriteメソッドです。


###DatabaseSessionHandler.php

```php
public function write($sessionId, $data)
    {
        $payload = $this->getDefaultPayload($data);
    ~略~

```

続きましてgetDefaultPayloadメソッド
payloadにエンコードした$data,last_activityにcurrentTime(),つまり現在時間を代入している事が見て取れますね。

###Store.php
```php
 public function save()
    {
        $this->ageFlashData();
        dd($this->attributes);
        $this->handler->write($this->getId(), $this->prepareForStorage(
            serialize($this->attributes)
        ));

        $this->started = false;
    }


```


###DatabaseSessionHandler.php

```php
 $payload = [
            'payload' => base64_encode($data),
            'last_activity' => $this->currentTime(),
        ];
~略~

```

$dataがどのような値を確認するには
DatabaseSessionHandler.phpを呼び出し、writeを実行しているStore.phpを確認します。attributesを確認することで$dataが確認できます。とりあえずdumpします。
tokenなどの値が格納されている事が確認できますね。



payloadの中身
![スクリーンショット 2021-06-10 22.16.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/85300fc0-c19b-0a9a-cf99-e2eb5c7f85cb.png)


では非ログイン状態で値を確認したいと思います。dumpだと処理が流れてしまうのでddで出力します。
存在しないユーザーなのでerrorsにはThese credentials do not match our records.と出力されていますね。バリデーションエラーもSeeeionの中に保存されます。
登録されていないユーザでアクセスしますと以下が結果として出力されます。

![スクリーンショット 2021-06-10 22.27.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/5e667290-794d-2fa9-c6d7-31129cbd16dc.png)

次にSessionに値を入れた場合にpayloadがどのように変化するか見てみます。
ページのトップに接続した際にsessionヘルパー関数を利用してtestを追加します。

自分の場合ルーティングが以下なので

###web.php
```php
Route::resource('/',VotingController::class);
```

以下のコントローラに値をセットします。
###VotingController.php
```php
 session()->put('test','確認ですよー');
```

ページに飛んで値を確認します。
![スクリーンショット 2021-06-10 22.48.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/826735/494360f9-f982-1c13-79c2-cc52bfaa2f47.png)

test=>確認ですよーがセッションに保存されていますね。
Sessionに値を設定するとpayloadに保存される事になります。

なんとなくですが、cookieとsessionの挙動の流れが理解できたかなーて感じです。


###CookieのXSRF_TOKENとは
ブラウザ側に保存されているCookieの中には、SessionIDを保存したLaravel_sessionとXSRF_TOKENがありました。SessionIDについてはこれまでの流れでなんとなくわかりましたが、XSRF_TOKENとは何なのでしょうか??

XSRF_TOKENとはCSRF(クロスサイトリクエストフォージェリ)対策に使用するためのTOKENデータです。通常のPOSTアクセスではフォームに埋め込んだCSRFトークンを利用して、その値をチェックする事でフォームを送ってきたページが正しいページなのかを判断します。XSRF_TOKENは入力フォームではなくaxiosを利用してpostリクエストを送信する際に利用します。

XSRT_TOKENとCSRF_TOKENがありますが、両者の違いは暗号化を行っているかどうかです。XSRF_TOKENは暗号化されているためLaravel側ではmiddlewareのVerifyCsrfTokenに復号化を行う処理が入っています。

Laravelのデフォルトでaxiosを利用することが可能です。デフォルトの設定のままLaravelを利用すればXSRF_TOKENを自動でheaderに組み込んでPOSTリクエストを送信してくれます。XSRF_TOKENとaxiosのおかげでaxiosを利用する場合はcsrfの設定する必要はありません。ajaxを利用する場合はmetaタグでcsrfトークンを生成してその値をHeaderに追加してLaravelに送信する必要があります。


とりあえず以上がSessionとcokieの記事でした。自分も理解していないことが多くありますのでもっと理解が深まったら記事を更新したいと思います。

間違った表現等もございますのでご指摘いただければと思います。