ブラウザのストレージとキャッシュの基礎
Cookie・localStorage・sessionStorage・ブラウザキャッシュの違いと使い分けを整理する
TL;DR
- Cookie・localStorage・sessionStorage はいずれもブラウザ上にデータを保存する仕組みだが、寿命・容量・HTTPリクエストへの付与が異なる
- 認証トークンの保存先は「XSSリスクを取るか、CSRF リスクを取るか」の判断になる。どちらも完全に安全ではない
- ブラウザキャッシュはこの3つとは別物で、HTTPレスポンスそのものを再利用する仕組みであり、サーバー側の
Cache-Controlヘッダーで制御する
4つの仕組みの概要
graph LR
subgraph browser ["ブラウザ"]
A["Cookie"]
B["localStorage<br/>(JS からのみ操作)"]
C["sessionStorage<br/>(JS からのみ操作)"]
D["ブラウザキャッシュ"]
end
subgraph server ["サーバー"]
E["Webサーバー / API"]
end
A -- "HTTPリクエストに自動付与" --> E
D -- "キャッシュヒット時はリクエスト省略" --> E
Cookie
Cookie(クッキー)は、サーバーがブラウザに小さなデータを預け、以降のリクエストで自動的に返送させる仕組みである。HTTP ヘッダー(Set-Cookie / Cookie)で送受信する。
たとえばログイン処理では次のように使われる。
sequenceDiagram
participant B as ブラウザ
participant S as サーバー
B->>S: POST /login(メールアドレス・パスワード)
S-->>B: 200 OK<br/>Set-Cookie: session_id=abc123 HttpOnly Secure
Note over B: Cookie を保存
B->>S: GET /mypage<br/>Cookie: session_id=abc123
S-->>B: 200 OK(ログイン済みユーザーのページ)
ブラウザは Set-Cookie で受け取った値を保存し、同じドメインへの次のリクエストに Cookie ヘッダーとして自動で付与する。サーバーはその値を見て「このリクエストは誰のものか」を判断できる。
特徴
- 寿命:
ExpiresまたはMax-Ageで指定する。省略するとセッションが終わる(ブラウザを閉じる)まで - 容量:4 KB 程度(ドメインごと)
- HTTP リクエストへの付与:同一ドメインへのリクエストに自動で付与される
- JS からのアクセス:
document.cookieで読み書きできるが、HttpOnlyフラグを付けると JS からアクセス不可になる
Set-Cookie の主なフラグ
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict; Max-Age=3600| フラグ | 意味 |
|---|---|
HttpOnly | JS から読み取り不可。XSS でトークンを盗まれるリスクを下げる |
Secure | HTTPS 通信のときのみ送信する |
SameSite=Strict | 別サイトからのリクエスト(フォーム送信など)には Cookie を付けない。CSRF 対策になる |
SameSite=Lax | 別サイトからのナビゲーション(リンクのクリック)では付けるが、フォーム POST には付けない |
SameSite=None | クロスサイトのリクエストにも付ける(Secure 必須) |
Max-Age=N | N 秒後に削除する |
localStorage
localStorageは、ブラウザ上に半永久的にデータを保存できるキー・バリュー型の API である。
特徴
- 寿命:明示的に削除するまで残る(ブラウザを閉じても消えない)
- 容量:5 MB 程度(ブラウザ・オリジンごと)
- HTTP リクエストへの付与:自動では付与されない。JS で明示的に取り出してヘッダーに入れる必要がある
- JS からのアクセス:
localStorage.setItem / getItem / removeItemで操作する - スコープ:オリジン(スキーム + ドメイン + ポート)単位。同一オリジン内のすべてのタブで共有される
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");sessionStorage
sessionStorageは localStorage と同じキー・バリュー型の API だが、タブ(セッション)ごとに独立している点が異なる。
特徴
- 寿命:タブを閉じると削除される
- 容量:5 MB 程度
- HTTP リクエストへの付与:しない
- JS からのアクセス:
sessionStorage.setItem / getItem / removeItem(localStorage と同じ API) - スコープ:タブ単位。同じオリジンでも別タブとはデータを共有しない
sessionStorage.setItem("wizard_step", "2");
const step = sessionStorage.getItem("wizard_step"); // "2"localStorage と sessionStorage の使い分け
API は同一なので、どちらを使うかは「データをどのスコープで生かすか」だけで決まる。
判断の起点は「タブをまたぐか/閉じても残すか」
graph TD
Start["データを保存したい"]
Start --> Q1{"タブを閉じたら<br/>リセットしてよいか?"}
Q1 -- "残したい" --> Q2{"複数タブで<br/>同じ値を共有したいか?"}
Q1 -- "消えてよい" --> Q3{"複数タブで<br/>状態が混ざっては困るか?"}
Q2 -- "共有したい" --> LS["localStorage"]
Q2 -- "タブ別でよい" --> Q3
Q3 -- "困る" --> SS["sessionStorage"]
Q3 -- "混ざっても問題ない" --> SS2["sessionStorage<br/>(または localStorage でも可)"]
具体的なユースケース
localStorage を選ぶケース
UI 設定(テーマ・言語・フォントサイズ)
ユーザーが「ダークモード」に切り替えたら、次回アクセス時も維持したい。別タブでも同じ設定を使いたい。
// ダークモード切り替え
localStorage.setItem("theme", "dark");
// 次回アクセス時に復元
const theme = localStorage.getItem("theme") ?? "light";
document.documentElement.setAttribute("data-theme", theme);ゲストユーザーのカート
ログインしていないユーザーが商品をカートに入れた状態を、ブラウザを閉じても保持したい。別タブで同じカートを参照・編集できるようにしたい。
const cart = JSON.parse(localStorage.getItem("cart") ?? "[]");
cart.push({ id: 42, name: "商品A", qty: 1 });
localStorage.setItem("cart", JSON.stringify(cart));sessionStorage を選ぶケース
多ステップフォーム(申し込みウィザード)
「Step 1 → Step 2 → Step 3 → 確認 → 送信」のように複数ページにまたがるフォームの途中入力を保持したい。タブを閉じたらリセットしてほしい。また、同じ申し込みフォームを別タブで開いたときに入力内容が混ざっては困る。
// Step 1 完了時に保存
sessionStorage.setItem("apply_step1", JSON.stringify({ name: "山田太郎", email: "..." }));
// Step 2 でStep 1 の値を参照
const step1 = JSON.parse(sessionStorage.getItem("apply_step1") ?? "{}");ページ内の一時的なフィルター・ソート状態
「カテゴリ:電化製品、並び順:価格昇順」のような検索条件を、ページ遷移をまたいで保持したい。ただしブラウザを閉じたらデフォルトに戻ってほしい。別タブで別の検索をしている場合は互いに干渉させたくない。
sessionStorage.setItem("filter", JSON.stringify({ category: "electronics", sort: "price_asc" }));まとめ
| localStorage | sessionStorage | |
|---|---|---|
| 次回アクセスも残す | ○ | ✗(タブを閉じると消える) |
| 別タブで共有 | ○ | ✗(タブごとに独立) |
| タブを閉じてリセット | ✗(明示削除が必要) | ○ |
| タブごとに独立した状態 | ✗ | ○ |
| 向いている用途 | 設定・カート・長期保持データ | ウィザード・一時フィルター・タブ固有状態 |
ブラウザキャッシュ
ブラウザキャッシュは、一度取得した HTTP レスポンス(HTML・JS・画像など)を再利用する仕組みである。上記 3 つとは性質が異なり、JS から直接操作するものではなく、サーバーの Cache-Control ヘッダーによって制御される。
詳細な仕組みは Cache-Control ヘッダーの基礎 に記載している。
概要
sequenceDiagram
participant B as ブラウザ
participant C as ブラウザキャッシュ
participant S as サーバー
B->>S: GET /app.js
S-->>B: 200 OK / Cache-Control: max-age=31536000
Note over C: レスポンスを保存
B->>C: GET /app.js(2回目)
C-->>B: キャッシュから即返却(サーバーへのリクエストなし)
Cookie・localStorage との違い
ブラウザキャッシュが保存するのは「HTTP レスポンス」全体(ヘッダーを含む)であり、アプリのデータを保存するものではない。用途がまったく異なる。
比較まとめ
| Cookie | localStorage | sessionStorage | ブラウザキャッシュ | |
|---|---|---|---|---|
| 寿命 | Expires / Max-Age で設定 | 明示削除まで | タブを閉じるまで | Cache-Control で設定 |
| 容量 | 約 4 KB | 約 5 MB | 約 5 MB | ブラウザ設定次第(数百 MB〜) |
| HTTP 自動送信 | あり | なし | なし | —(リクエスト自体をスキップ) |
| JS アクセス | 可(HttpOnly で禁止可) | 可 | 可 | 不可 |
| スコープ | ドメイン + パス | オリジン | タブ + オリジン | オリジン + URL |
| 操作主体 | サーバー / JS | JS | JS | サーバー(ヘッダーで制御) |
| 主な用途 | セッション、認証トークン | テーマ、ユーザー設定、永続トークン | 一時的なフォーム状態、ウィザード進捗 | 静的ファイル、API レスポンスの再利用 |
使い分けの指針
認証トークンをどこに保存するか
認証トークン(セッション ID・JWT など)の保存先は、XSS と CSRF の二つのリスクのトレードオフになる。
graph TD
A["認証トークンの保存先を選ぶ"]
A --> B["Cookie(HttpOnly + SameSite)"]
A --> C["localStorage / sessionStorage"]
B --> D["XSS でトークンを盗まれにくい<br/>(JSから読めない)"]
B --> E["CSRF 攻撃のリスクがある<br/>(SameSite=Strict で対策可)"]
C --> F["CSRF リスクはない<br/>(自動送信されないため)"]
C --> G["XSS 攻撃でトークンを盗まれるリスクがある"]
一般的には Cookie(HttpOnly + Secure + SameSite=Strict)が推奨される。XSS の防止が難しい環境では、それでも JS でトークンを読む必要がないため被害を限定できる。
ケース別の選択
| ケース | 推奨 |
|---|---|
| セッション ID・認証トークン | Cookie(HttpOnly + Secure + SameSite) |
| UI 設定(テーマ・言語など)・永続的なユーザー設定 | localStorage |
| 複数タブで共有してほしくない一時データ(フォームの途中入力、ウィザードの状態) | sessionStorage |
| 静的ファイル(JS・CSS・画像)の配信最適化 | ブラウザキャッシュ(Cache-Control) |
| API レスポンスの短期キャッシュ | ブラウザキャッシュ(Cache-Control) |
よくある落とし穴
HttpOnly を付け忘れた Cookie
HttpOnly なしの Cookie は document.cookie で JS から読める。XSS が成立するとトークンを盗まれる。認証 Cookie には必ず HttpOnly を付ける。
SameSite を設定していない Cookie
SameSite 未設定(または None)の Cookie は、他サイトからのリクエストにも付与される。これが CSRF の起点になる。現代のブラウザは未設定の場合 Lax として扱うことが多いが、明示的に指定するのが望ましい。
localStorage にトークンを入れて「安全」と思い込む
CSRF を回避できるが XSS には無防備である。XSS を防げていなければ、localStorage の内容はすべて漏洩する。どちらを使う場合も、XSS 対策(CSP ヘッダー、入力サニタイズ、信頼できないスクリプトの排除)は必須。
sessionStorage を「セッション Cookie と同じ」と思い込む
sessionStorage のスコープはタブ単位である。別タブで同じサイトを開いてもデータは共有されない。「ブラウザを閉じたら消える」点はセッション Cookie と同様だが、「タブを閉じただけで消える」点が異なる。