コンテンツにスキップ

JWTはどこに保存すべきか:localStorage・sessionStorage・HTTP-only Cookie・メモリの比較

概要

JWT を「どこに保存するか」はセキュリティに直結する設計上の重要な選択。4つの選択肢それぞれのリスクとメリットを整理する。

詳細

4つの保存場所の比較

保存場所 XSS 耐性 CSRF 耐性 永続性 推奨度
localStorage ✗ 弱い ✓ 強い ページ間で維持
sessionStorage ✗ 弱い ✓ 強い タブを閉じると消える
HTTP-only Cookie ✓ 強い ✗ 弱い(対策必要) Expires まで維持 ✓ 推奨
Memory only ✓ 強い ✓ 強い リロードで消える ✓ 推奨

A. localStorage(非推奨)

localStorage.setItem('token', jwt);  // 保存
const token = localStorage.getItem('token');  // 取得

問題: JavaScript から読み取れるため、XSS 攻撃で盗まれる。 - サードパーティスクリプトや注入された JS が localStorage を読める

B. sessionStorage(非推奨)

localStorage と同じ XSS リスク。タブを閉じると消えるだけで本質的な違いはない。

Set-Cookie: token=<jwt>; HttpOnly; Secure; SameSite=Strict; Path=/
  • HttpOnly: JavaScript から読めない(XSS 対策)
  • Secure: HTTPS のみ送信
  • SameSite=Strict: CSRF を大幅に緩和

残るリスク: CSRF(クロスサイトリクエストフォージェリ) → 対策: SameSite=Strict + CSRF トークンの二重チェック

D. Memory only(最も安全)

// React の例: Zustand や Context に保存
let accessToken = null;  // モジュール変数

export function setToken(token) { accessToken = token; }
export function getToken() { return accessToken; }

XSS も CSRF も防げるが、ページリロードで失われる。 → リフレッシュトークンを HTTP-only Cookie に置き、リロード時に再発行する設計が現実的。

推奨アーキテクチャ

Access Token  → メモリ(有効期限: 15分〜1時間)
Refresh Token → HTTP-only Cookie(有効期限: 7〜30日)

リロード時:
  1. Refresh Token Cookie を使って /auth/refresh を呼ぶ
  2. 新しい Access Token を取得してメモリに保存

なぜ重要か / いつ使うか

  • 認証機能を設計・実装するとき
  • セキュリティレビューで「トークンはどこに置いてるの?」と聞かれたとき
  • 面接で「JWT の安全な扱い方」を聞かれたとき