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

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

本文書を読み終えるとSession、Cookieにはどのような情報が保存されているのか。middlewareがSessionとCookieに与える影響を理解することができます。

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

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

Session Driverの変更

先ほどサーバ側ではSession IDをSessionに保存すると説明しましたが、サーバのどこに保存するかをSession Driverを使って設定します。保存できる場所にはデフォルトのファイルの他にデータベース、Cookie、redisなどさまざまな場所がありますが今回はデータベースを利用します。変更は.envファイルで行うことができます。

.envファイルのSESSION_DRIVERを下記のようにdatabaseに変更します。


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

セッションとクッキー

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

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

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

ブラウザ側でCookieの中身を確認します。使ってみるとCookieの情報が読みやすかったのでここではCookie ManagerというChomeのExtentionを利用しています。

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

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

Cookieの中身
Cookieの中身

サーバ側でSession情報を保存するsessionsテーブルにはどのような情報が保存されているのかも確認してみましょう。

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

データベース管理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を保持すると説明しましたが、sessionsテーブルの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のどちらの値もEncrpytCookiesをコメントアウトする前より明らかに短くなっていることが確認できます。

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

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

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

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

last_activityとExpirationの確認

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

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

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

この数字はunixタイムスタンプと言われる数字で変換を行うと実行した日付に戻すことができます。last_activityは最後のアクティビティという意味を持つのでページにアクセスする毎に更新されることがわかります。

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

Expirationの確認
Expirationの確認

しかしCookieのExpirationは現在の時刻ではなく現在の時刻の2時間後になっています(各自の環境でアクセス時間+2時間になっていることを確認)。なぜ、有効期限が2時間後なのでしょうか。これはデフォルトで有効期限が2時間に設定されているためです。

Sessionに関する設定は、すべてconfig/Session.phpファイル内に記述されています。有効期限はlifetimeパラメータで設定を行うことができるので確認してみましょう。デフォルトでは120に設定がされています。単位は分です。


'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のlast_activityとCookieのexpirationの確認でそれらの時刻がなにを意味するのかとアクセス毎に常にそれらの情報が更新されていることがわかりました。

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

ユーザがログインしたらどのように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がログインしたユーザのIDに設定されるということがわかりました。

Sessionsテーブルのpayload

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

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

payloadがbase64でエンコードされるのはSession DriverをDatabaseに変更した場合に行われます。fileやcookieでは行われません。そのほかのドライバーは未確認です。

Sessionの処理はmiddelwareのStartSession.phpを使って行われているのでその処理をコードを追っていきます。StartSession.phpはkernel.phpファイルで確認できます。


protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class, //ここ

今回はデータベースを利用しているのでDatabaseSessionHandler.phpの中でSessionsテーブルへの書き込みがwriteメソッドで行われます。そのwriteメソッドで渡される$dataがbase64でencodeされています。


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

$dataがどのような値を保持しているのかを確認するためにDatabaseSessionHandler.phpを呼び出しwriteメソッドを実行しているIlluminate¥Session¥Store.phpのsaveメソッドの中で$dataは、$this->attribuetesだということがわかるので、$this->attributesの中身を確認します。


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

    dd($this->attributes);

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

    $this->started = false;
}

ddで$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リクエストを送信してくれます。

VerifiCsrfTokenのmiddlewareをコメントアウトするとCookieにはXSRF_TOKENは保存されなくなります。

ファイルに保存された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();