Session(セッション)やCookie(クッキー)がどのように使われているか理解していないまたはSessionやCookieの仕組みはなんとなく理解できているけど実際にどのような情報がやり取りしているのかわからないという人を対象に”Laravel”でのSessionとCookieについて説明を行っています。

デフォルトではLaravelのSession情報はファイルに保存されますが今回はテーブルを使って確認します。テーブルにSession情報を書き込むとSession情報が一括で確認でき、削除や更新が可能なためです。

SessionとCookie

HTTPはステートレスなため同じユーザがあるページを閲覧し、次のページに移動すると同じユーザがアクセスしているかどうかサーバ側ではわかりません。もしシステムにログインしたとしても次のページに移動した瞬間にログイン状態はなくなるため会員システムのようなアプリケーションを構築することができません。

この問題を解決するために存在するのがSessionです。クライアントであるブラウザとサーバ側(ここではLaravel)がSession IDを共有し、そのSession IDを照会することでユーザを識別することが可能になります。

ブラウザ側でSession IDを保存する場所がCookieとなり、サーバ側でSession IDを保持する場所がSessionになります。

上記で説明したことを実際にLaravelを利用して動作確認を行うことでSessionとCookieがどのような役目を果たしているかを理解を深めることができます。

環境構築

sessionsテーブルの作成を行うため、事前にデータベース、usersテーブルの作成、ログイン認証の機能が使える状態にしておく必要があります。本文書では環境設定については割愛しています。

Sessionsテーブルの作成

Laravelのマニュアルのsessionsテーブルのスキーマを参考に作成します。

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


$ php artisan make:migration create_sessions_table

database¥migrationsディレクトリの下にマイグレーションファイルが作成されるので、ファイルに以下の情報を記述します。


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 ($table) {
            $table->string('id')->unique();
            $table->unsignedInteger('user_id')->nullable();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->text('payload');
            $table->integer('last_activity');
        });
    }

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

sessionテーブルには、id, user_id, ip_address, user_agent, payload, last_activityの列を作成しています。それぞれの列にどのような情報が入るかは動作確認を行う中で理解することができます。

Session Driverの変更

デフォルトではSession Driverはファイルに設定されています。ファイルからデータベースへ変更を行います。変更は.envファイルで行うことができます。

SESSION_DRIVERを下記のようにdatabaseに変更してください。


SESSION_DRIVER=database
.envファイルの設定変更を反映させるためにはphp artisan config:clearを実行してください。それでも反映されない場合はphp artisan serveコマンドで開発サーバを起動しなおしてください。

セッションとクッキー

セッションとクッキーを目で確認

LaravelのSessionとCookieにはどのような情報が記述されるのか実際に目でみてみましょう。

ブラウザを起動して、”/”にアクセスします。アクセスする際はLaravelへのログインを行っていない状態でアクセスします。

ブラウザ側でCookieの中身を確認します。ここではCookie ManagerというChomeのExtentionを利用しています。

Chromeの詳細設定からもCookieの中身は確認できます。

アクセスしたときに作成されるCookieは以下の通りです。XSRF-TOKENとlaravel_sessionの2つを確認することができます。Valueには文字列が入り、Expiration列には時刻が表示されています。

Cookieの中身
Cookieの中身

sessionsテーブルにはどのような情報が記述されているのかも確認してみましょう。

sessionテーブル内の情報
sessionテーブル内の情報

データベース管理GUIにはTablePlusを利用しています。

テーブルには作成時に追加したid, user_id, ip_address, user_agent, payload,last_actitity列が確認できます。user_id以外はすべて埋まっていることが確認できます。ip_addressにはアクセスしたIPアドレス、user_agentにはブラウザの情報が入っています。use_id, payload, last_activityについてはこれから説明を行っていきます。

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

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

middleware(ミドルウェア)とは

middleware(ミドルウェア)はブラウザからLaravelのページにアクセスした際に必ず実行される処理です。

middlewareの処理はapp¥http¥kernel.phpに記述されておりその処理の中には、Sessionの開始やCookieの暗号化やTOKENの確認なでの処理が含まれています。

middlewareについての詳細はまた別の機会に説明を行う予定です。

クッキーの暗号化を解除

middlewareに設定されている処理は処理毎にわけられているので取り外しが可能です。Cookieの暗号化の処理を確認するために暗号化の機能を外してみましょう。

app¥http¥kernel.phpファイルを開いてEncryptCookiesをコメントアウトしてください。


'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,
],

コメントアウトが完了したら、再度ブラウザでアクセスしてください。Cookieの値を見ると先程より明らかに短くなっていることが確認できます。

暗号化を行わない状態
暗号化を行わない状態

sessionsテーブルを確認してみましょう。sessionsテーブルの2行目のid(BiaGPbarYRd9TwHFdfo70omcCUdLuCddN5vN9TEF)がCookieの中に入っているlaravel_session値と同じになっていることが確認できます。

Sessionテーブルのidを確認
Sessionテーブルのidを確認

この結果からCookieとSessionで同じIDが保持できていることが確認できました。

last_activityとExpirationの確認

ユーザを作成するためにRegisterリンクをクリックしてユーザ登録画面に移動します。

Session情報のlast_actitity列を確認してみてください。Cookieの暗号化を確認した時よりも数字が増えていることが確認できます。少しおいて画面のブラウザをリロードしてみてください。さらに数字が増えていることが確認できます。

last activity列を確認
last activity列を確認

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

Cookie側にも時間に関するExpiration(有効期限)を持っています。この値もページにアクセスする毎に増えていることが確認できます。

Expirationの確認
Expirationの確認

ここまでのSessionのlast_activityとCookieのexpirationの確認でアクセス毎に常にそれらの情報が更新されていることがわかりました。

CookieのExpirationは現在の時刻の2時間後になっています。なぜ、有効期限が2時間後なのでしょうか。これはデフォルトで有効期限が2時間に設定しているためです。

Sessionに関する設定は、すべてconfig/Session.phpファイル内に記述されています。有効期限はlifetimeパラメータで設定を行うことができます。


'lifetime' => env('SESSION_LIFETIME', 120),

.envファイルのSESSION_LIFETIMEパラメータで変更することが可能です。2時間から4時間に変更してみましょう。120から240に変更してください。単位は分です。

.envファイルの設定変更を反映させるためにはphp artisan config:clearを実行してください。それでも反映されない場合はphp artisan serveコマンドで開発サーバを起動しなおしてください。

現時刻が16時すぎのためアクセスを行うと18時の2時間後から20時の4時間後に変更になっていることが確認できます。

有効期限を変更
有効期限を変更

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

ユーザがログインしたらどのようにSessionの情報が変わるのか確認してみましょう。ユーザ登録を行い、そのユーザでログインを行ってください。

ユーザがログインした瞬間にcookieのlaravel_sessionの値が更新されることを確認してください。

IDが更新される
IDが更新される

また、sessionsテーブルを見るとCookieと同じIDを持っていますが、user_idに値が入っているのがわかります。ログインする前はuser_idはNULLでした。このIDはログインを行ったユーザのIDです。別のユーザでログインすると別のユーザのIDがuser_idに入ります。

user_idに値が入る
user_idに値が入る

ログインを行うことでSessionのuser_idが設定されるということがわかりました。

Sessionテーブルのpayload

sessionテーブルのpayload以外の列の情報についてはここまでの動作確認で理解することができました。次はpayloadの内容を確認していきます。

payloadもidと同様に長い文字列なので暗号化されているかと思うかもしれませんが、暗号化ではなくbase64でエンコードが行われています。base64でエンコードされる前のデータが何なのかを確認していきます。

Sessionの処理はmiddelwareのStartSession.phpを使って行われているのですが、その処理の中でsessionテーブルに書き込む処理の箇所でエンコードが行われています。payloadの中身はこの$dataということになります。


protected function getDefaultPayload($data)
      {
          $payload = [
              'payload' => base64_encode($data),
              'last_activity' => $this->currentTime(),
          ];

$dataがどのような値を保持しているのかを確認するためにIlluminate¥Session¥Store.phpを開いてsaveメソッドの中で$this->attribuetesの内容を取得します。


public function save()
{
    $this->ageFlashData();

    dd($this->attributes);

    $this->handler->write($this->getId(), $this->prepareForStorage(
        serialize($this->attributes)
    ));

    $this->started = false;
}

$this->attribuetesを確認するとtokenなどの値が保存されていることがわかります。また入力フォームで入力した値を保持する_flashのold, newもpayloadに保存されていることがわかります。

payloadの中身
payloadの中身

_flashの値がどのように変化するのか意図的にログインユーザに登録されていないユーザでログインを行って、値を確認したのが下記です。

_flashのoldにも値が入り、errorsも追加されています。errorにはThese credentials do not match our recodesも確認できます。バリデーションエラーもSessionの中に保存されることがわかります。

登録されていないユーザでアクセス
登録されていないユーザでアクセス

Sessionに値を入れる

次にSessionに値を登録した場合にpayloadがどのように変化するのか確認してみましょう。

web.phpファイルを開いてsessionヘルパー関数を使ってSessionにtestを追加します。設定後、ブラウザでアクセスしてSessionにtestが登録しても画面には何も変化はありません。


Route::get('/', function () {
    session()->put('test','セッションにデータ追加');
    return view('welcome');
});

Sessionにtestを追加した後に$this->attribuetesの中身を確認します。


public function save()
{
    $this->ageFlashData();

    dd($this->attributes);

    $this->handler->write($this->getId(), $this->prepareForStorage(
        serialize($this->attributes)
    ));

    $this->started = false;
}

新たにtestが追加されていることが確認できます。このように英数字のただの羅列だと思われていたsessionsテーブルのpayload列はSessionに保存されている値だということがわかりました。Sessionになにか値を設定するとこのpayloadに保存されることになります。

sessionにtestを追加した後の確認
sessionにtestを追加した後の確認

CookieのXSRF_TOKENについて

ブラウザ側に保存されているCookieの中には、Session IDを保存したLaravel_sessionとXSRF_TOKENの2つがありました。Session IDについてはこれまでの説明で理解することができましたが、XSRF_TOKENはなんなのでしょう。

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

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

Laravelのデフォルトでaxiiosを利用することが可能です。デフォルトの設定のままLaravelを利用すればXSRF_TOKENを自動でheaderに組み込んでPOSTリクエストを送信してくれます。

ファイルに保存されたsession情報

SESSION DRIVERをdatabaseからfileに変更し、fileにはどのようにSession情報が入っているか確認しておきましょう。ファイルの保存場所はstorage/framework/sessionsディレクトリにSession毎に1つのファイルとして保存されます。ファイル名はSession IDになっています。

中身を確認すると下記のように記述されています。base64でエンコードされているわけではありません。


a:3:{s:6:"_token";s:40:"Zl5eRTQtr431ZKIA8XuoGk1FVFOQQVRlzKBFeYzQ";s:9:"_previous";a:1:{s:3:"url";s:21:"http://localhost:8000";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}

Session情報の表示

Sesson情報はsessionヘルパー関数を利用して取得することが可能です。すべての保存されている値を一括で見たい場合は下記で取得することが可能です。


session()->all();