Webアプリケーションでは、一度ログインすると、別のページへ移動してもログイン状態が続きます。
ここで、ふと疑問に思いました。
この「ログイン済み」という状態は、どこにあるんだっけ?
ブラウザが覚えているのでしょうか。
サーバーが覚えているのでしょうか。
それともHTTPがいい感じに覚えてくれているのでしょうか。
認証まわりを学んでいると、よく次のような説明を見かけます。
HTTPはステートレスなので、ログイン状態を維持するために
CookieやSessionを使います。
「ステートレス」とは何か。
なぜHTTPは状態を持たないのか。
CookieとSessionは、具体的に何を補っているのか。
この記事では、「ログイン済みって、どこにあるの?」という疑問を出発点に、HTTPのステートレス性、Cookie、Sessionの役割を整理します。
この記事では一般的なSession認証を前提に進めていくので、JWTなどの
トークンベース認証やCookie自体に状態を持たせる設計については扱いません。
1. まず、ステートとは何か
ステートとは「状態」のことです。
Webアプリケーションにおける状態とは、たとえば次のような情報です。
- このユーザーはログイン済みか
- このリクエストは誰から来たのか
- このユーザーはどの権限を持っているのか
- カートにどの商品が入っているのか
- 入力途中の情報があるか
例えばECサイトでは、商品をカートに入れたあと、別の商品ページへ移動してもカートの中身は残っていてほしいです。
また、ログイン後にマイページへ移動したときは、サーバーに「この人はログイン済みのユーザーだ」と判断してほしいです。
つまり、状態とはざっくり言うと、
アプリケーションが「今このユーザーをどう扱うべきか」を判断するための情報
だと考えるとわかりやすいです。
2. HTTPはステートレス
ステートレスとは、「状態(ステート)を持たない」という意味です。
もう少し具体的にいうと、HTTPのリクエストは基本的に1回ごとに独立しています。
例えば、次のようなリクエストがあったとします。
1回目:ログインする
2回目:マイページを見る
3回目:設定画面を見る
人間から見ると、同じユーザーが続けて操作しているように見えます。
しかしHTTPそのものは、2回目のリクエストを見ただけで、
この人は1回目でログインした人だ
と自動的に判断してくれるわけではありません。
HTTPはリクエストに対してレスポンスを返す通信の仕組みであり、前回と今回のリクエストを自動で結びつける機能は持っていません。
3. ステートレスなのはブラウザではなくHTTP
ここで最初に私が勘違いしていたのが、次のような理解です。
ブラウザが状態を覚えられないから、CookieやSessionが必要なのかな?
しかし、これは正確ではありません。
ブラウザ自体は、Cookie、キャッシュ、LocalStorage、履歴など、さまざまな情報を保持できます。
状態を自動で覚えないと言われているのは、ブラウザではなくHTTPです。
より正確には、
HTTPという通信の仕組みが、前回のリクエストと今回のリクエストを自動では結びつけない
ということです。
ここを分けて考えると、かなり理解しやすくなります。
ブラウザ:
情報を保存できる
HTTP:
リクエストとレスポンスをやり取りする通信ルール
リクエスト同士の状態を自動では管理しない
「ログイン状態を覚える」という話は、ブラウザだけの話でもHTTPだけの話でもありません。
4. HTTPが状態を持たない理由
では、なぜHTTPは状態を持たないのでしょうか?
最初は、ログインしたことくらいHTTPが覚えておいてくれればいいのに・・・
と思っていました。
しかし、HTTPはWeb全体で使われる共通の通信ルールです。
Webには、さまざまなアプリケーションがあります。
- ニュースサイト
- ECサイト
- SNS
- 動画サイト
- 社内システム
- 管理画面
- APIサーバー
それぞれ必要な状態は違います。
ECサイトならカート情報が必要かもしれません。
SNSならログインユーザーや通知状態が必要かもしれません。
一方で、静的なページや画像を返すだけなら、ユーザーごとの状態が不要な場合もあります。
もしHTTP自体が、すべてのアプリケーションの状態管理まで抱え込む仕組みだったら、通信のルールそのものが複雑になります。
また、サーバーを複数台に増やす場合にも、HTTP自体がユーザーごとの状態を抱え込んでいると、どのサーバーがどのユーザーの状態を持っているのかを意識する必要が出てきます。
そこでHTTPは、リクエストとレスポンスをやり取りするための基本的な仕組みに集中しています。
そして、ログイン状態やカート情報のようなアプリケーションごとの状態は、各アプリケーションが必要に応じて管理します。
つまりHTTPは、状態管理を何も考えていないのではなく、
Web全体で使える共通の通信ルールとして、あえて役割を絞っている
ということです。
5. でもWebアプリには状態が必要
HTTP自体はステートレスですが、Webアプリケーションには状態が必要です。
ログイン機能を考えると、サーバーは次のことを判断する必要があります。
- このリクエストは誰から来たのか
- このユーザーはログイン済みなのか
- このユーザーはこの画面を見てもよいのか
HTTPだけでは、前回ログインしたユーザーかどうかを自動では判断できません。
でも、Webアプリケーションとしては判断しなければいけません。
ここにギャップがあります。
HTTP自体は状態を持たないが、
Webアプリケーションには状態が必要
このギャップを埋めるために、CookieとSessionが登場します。
6. Cookie:ブラウザ側の目印
HTTPだけでは、前回のリクエストとのつながりがわからないので、ブラウザ側に「次回以降も持ってきてもらう目印」を保存します。
それがCookieです。
流れとしては、次のようなイメージです。
サーバー:
この目印を保存しておいてください
ブラウザ:
わかりました
次回以降のリクエスト
ブラウザ:
前にもらった目印を付けてリクエストします
サーバー:
この目印、前に渡したものだな
Cookieは、サーバーがブラウザに保存させ、後続のリクエストで送り返してもらえる情報です。
Cookieがあることで、サーバーはリクエストを見たときに、
前に渡した目印を持っているブラウザだ
と判断できます。
ただし、ブラウザ側に保存されるCookieへ、ログイン状態や権限のような重要な情報をそのまま入れて、サーバーが無条件に信用するのは危険です。
たとえば、次のようなCookieをそのまま信用するのは避けたいです。
userId=1
login=true
role=admin
Cookieはブラウザ側に保存されるため、ユーザーが内容を確認したり書き換えたりできる可能性があります。
その値をサーバーがそのまま信用してしまうと、なりすましや権限の書き換えにつながるおそれがあります。
そこで、一般的なSession認証では、Cookieにはユーザー情報そのものではなく、サーバー側の状態を探すためのIDを持たせます。
このIDがSession IDです。
7. Session:サーバー側の状態
Sessionは、サーバー側で管理するユーザーごとの状態です。
サーバー側では次のような情報を持ちます。
Session ID: abc123
userId: 1
login: true
role: user
ブラウザ側のCookieには、SessionそのものではなくSession IDを持たせます。
Cookie: SESSION_ID=abc123
そして次回以降、ブラウザはこのCookieをサーバーへ送ります。
サーバーはCookieに含まれるSession IDを見て、対応するSessionを探します。
このように、ブラウザはSessionそのものを持っているわけではありません。
ブラウザはCookieに入ったSession IDを送る。
サーバーはそのSession IDを使って、サーバー側のSessionを探す。
この組み合わせによって、ログイン済みかどうかを判断します。
Session IDは、ログイン状態に紐づく重要な情報です。
そのため、単なる文字列ではなく、
「これを持っていればログイン済みとして扱われうるもの」として、適切に管理する必要があります。
例えば、Session IDを入れるCookieには、JavaScriptから読み取られにくくする HttpOnly、HTTPS通信時のみ送信する Secure、有効期限の設定などを検討します。
つまり、役割分担としてはこうです。
Cookie:
ブラウザ側に保存される目印
Session:
サーバー側で管理される状態
Session ID:
CookieとSessionを結びつけるためのID
一般的なSession認証では、Cookieは状態そのものというより、サーバー側の状態を探すための鍵として使われます。
8. 「ログイン済み」の正体
ここまでを整理すると、「ログイン済み」とはブラウザにCookieがあることそのものではありません。
より正確には、次の状態です。
ブラウザから送られたCookieを手がかりに、
サーバーが有効なSessionを見つけられ、
そのSessionを認証済みユーザーのものとして扱える状態
つまり、ログイン済みとは、
ブラウザ側のCookie
+
サーバー側のSession
が紐づいて成立する状態です。
ブラウザが毎回IDとパスワードを送っているわけではありません。
HTTP自体がログイン状態を覚えているわけでもありません。
Cookieだけがログイン状態をすべて持っているわけでもありません。
それぞれの役割は次のように分かれています。
| 登場人物 | 役割 |
|---|---|
| HTTP | リクエストとレスポンスをやり取りする共通ルール |
| ブラウザ | Cookieなどの情報を保存し、必要に応じてリクエストに付ける |
| Cookie | ブラウザ側に保存される目印 |
| Session | サーバー側で管理するユーザーの状態 |
この役割分担によって、Webアプリケーションは「ログイン済み」という状態を扱えるようになります。
まとめ
この記事では、「ログイン済みって、どこにあるの?」という疑問から、HTTP、Cookie、Sessionの役割を整理しました。
今回のポイントは次のとおりです。
- ステートとは、アプリケーションが処理を判断するための状態
- HTTPはステートレスで、リクエスト同士を自動では結びつけない
- Cookieは、ブラウザ側に保存される目印
- Sessionは、サーバー側で管理するユーザーごとの状態
- 一般的なSession認証では、CookieはSession IDを持ち、サーバー側のSessionを探すための鍵になる
- ログイン済みとは、Cookieを手がかりにサーバーが有効なSessionを見つけられる状態
最初は「HTTPは状態を覚えてくれない不便な仕組み」だと思っていました。
しかし今は、HTTPがあえて役割を絞っているからこそ、さまざまなWebアプリケーションが同じ土台の上で通信できるのだと理解しています。
そして、各アプリケーションの状態は、CookieやSessionなどを使って必要に応じて拡張し管理する。
そう考えると、CookieとSessionはHTTPのステートレスを無理やり補うものではなく、
WebアプリケーションがHTTPの上で状態を扱うための自然な役割分担なのだと感じました。