Skip to content
Published on

SSO クッキー/JWT認証システム完全ガイド — フレームワーク別実践シリーズインデックス

Authors

シリーズ紹介 — なぜこのシリーズを書くのか

Web認証はシンプルではありません。「ログイン機能を1つ作ればいい」と考えがちですが、実践では以下のような疑問が次々と湧いてきます。

  • トークンをクッキーに入れるか、localStorageに入れるか?
  • JWTセッションのどちらを選ぶべきか?
  • HttpOnly CookieCSRFトークンをどう組み合わせるか?
  • **SSO(Single Sign-On)**環境で複数サービス間の認証状態をどう共有するか?
  • Access Token更新はフロントエンドで行うか、バックエンドで行うか?
  • CORScredentials設定はなぜいつも苦労するのか?

これらの質問の正解はフレームワークによって異なります。Spring BootのSecurityFilterChainとDjangoのSessionMiddlewareは基本戦略から違いますし、React SPAとNext.js SSRはクッキーを扱う方式自体が異なります。公式ドキュメントだけでは全体像を描くのが困難です。

このシリーズは認証システムの全体マップをまず広げた後、フレームワーク別に実践コードを実装する構成です。

シリーズが扱う範囲:

  • SSO / OAuth 2.0 / OIDCプロトコルの動作原理
  • クッキーベース認証とJWTベース認証の違い、長所短所、ハイブリッド戦略
  • ブラウザストレージ(Cookie、localStorage、sessionStorage、Memory)別のセキュリティ特性
  • CORS + Credential送信の正確な設定方法
  • Spring Boot、Django、React、Next.js各フレームワークでの実践実装
  • 複数フレームワークを組み合わせたハイブリッドアーキテクチャ設計

認証フロー全体マップ

認証システムの全体フローを一目で把握しましょう。以下はSSO/OIDCベースのログインからトークン更新、ログアウトまでの全プロセスです。

ログインフロー

┌──────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────┐
Browser  │     │  Frontend    │     │  Backend     │     │   IdP│(ユーザー)│      (React/Next) (Spring/ (Keycloak│
│          │     │              │     │  Django)     │     │  Okta)└────┬─────┘     └──────┬───────┘     └──────┬───────┘     └────┬─────┘
1. ログインクリック │                    │                  │
     │─────────────────▶│                    │                  │
     │                  │  2. /auth/login    │                  │
     │                  │───────────────────▶│                  │
     │                  │                    │  3. Redirect URL     │                  │                    │─────────────────▶│
4. IdPログインページ                    │                  │
     │◀──────────────────────────────────────────────────────────│
5. ユーザー認証(ID/PW, MFA)           │                  │
     │──────────────────────────────────────────────────────────▶│
     │                  │                    │  6. Auth Code     │                  │                    │◀─────────────────│
     │                  │  7. CodeToken   │                  │
     │                  │◀───────────────────│                  │
     │                  │                      (id_token +     │                  │                    │   access_token +     │                  │                    │   refresh_token)8. Set-Cookie:  │                    │                  │
     │     access_token │                    │                  │
          (HttpOnly)   │                    │                  │
     │◀─────────────────│                    │                  │
9. 認証完了、     │                    │                  │
     │     ダッシュボードへ │                    │                  │
     │◀─────────────────│                    │                  │

認証済みリクエストフロー

┌──────────┐     ┌──────────────┐     ┌──────────────┐
Browser  │     │  Frontend    │     │  Backend└────┬─────┘     └──────┬───────┘     └──────┬───────┘
APIリクエスト       │                    │
     │─────────────────▶│                    │
     │                  │  Cookie自動送信  (access_token)     │                  │───────────────────▶│
     │                  │                    │  JWT検証
     │                  │                      (署名、期限、クレーム)
     │                  │   200 + データ     │
     │                  │◀───────────────────│
     │  レンダリング       │                    │
     │◀─────────────────│                    │

トークン更新フロー

┌──────────┐     ┌──────────────┐     ┌──────────────┐
Browser  │     │  Frontend    │     │  Backend└────┬─────┘     └──────┬───────┘     └──────┬───────┘
APIリクエスト       │                    │
     │─────────────────▶│                    │
     │                  │  Cookie送信     │                  │───────────────────▶│
     │                  │   401 Unauthorized     │                  │◀───────────────────│
     │                  │                    │
     │                  │  POST /auth/refresh│
  (refresh_token    │
     │                  │   Cookie送信)     │                  │───────────────────▶│
     │                  │   新access_token   │
     │                  │   Set-Cookie     │                  │◀───────────────────│
     │                  │                    │
     │                  │  元のリクエスト再試行  │
     │                  │───────────────────▶│
     │                  │   200 + データ     │
     │                  │◀───────────────────│
     │  レンダリング       │                    │
     │◀─────────────────│                    │

ログアウトフロー

┌──────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────┐
Browser  │     │  Frontend    │     │  Backend     │     │   IdP└────┬─────┘     └──────┬───────┘     └──────┬───────┘     └────┬─────┘
     │  ログアウトクリック   │                    │                  │
     │─────────────────▶│                    │                  │
     │                  │  POST /auth/logout │                  │
     │                  │───────────────────▶│                  │
     │                  │                    │  Revoke Token     │                  │                    │─────────────────▶│
     │                  │   Set-Cookie:      │                  │
     │                  │   access_token=""  │                  │
     │                  │   Max-Age=0        │                  │
     │                  │◀───────────────────│                  │
     │  クッキー削除、     │                    │                  │
     │  ログインページへ    │                    │                  │
     │◀─────────────────│                    │                  │

核心ポイント:バックエンドがHttpOnly Cookieでトークンを管理すると、フロントエンドのJavaScriptはトークンに直接アクセスできません。これがXSS攻撃を防御する最も効果的な戦略です。


ブラウザストレージ別アクセス特性

トークンをどこに保存するかは、セキュリティと利便性のトレードオフです。各ストレージの特性を正確に理解して正しい選択をする必要があります。

ストレージJSアクセスサーバー自動送信XSS脆弱CSRF脆弱
HttpOnly Cookie不可あり安全あり
Non-HttpOnly Cookie可能あり脆弱あり
localStorage可能なし脆弱安全
sessionStorage可能なし脆弱安全
Memory(JS変数)可能なし一部安全

各ストレージの詳細分析:

  • HttpOnly Cookie:JavaScriptからアクセス不可能なのでXSSに安全です。ただし、ブラウザがすべてのリクエストに自動的にクッキーを添付するため、CSRF攻撃に脆弱です。SameSite属性CSRFトークンを必ず併用してください。
  • Non-HttpOnly Cookiedocument.cookieでアクセス可能なため、XSSとCSRFの両方に脆弱です。可能な限り使用しないでください。
  • localStorage:タブを閉じてもデータが保持されます。サーバーに自動送信されないのでCSRFには安全ですが、XSS攻撃でトークンが窃取される可能性があります。
  • sessionStorage:タブ単位で隔離され、タブを閉じると削除されます。セキュリティ特性はlocalStorageと同じです。
  • Memory(JS変数):ページ更新時に消滅します。XSSに完全に安全ではありません(実行中のスクリプトがアクセス可能)が、攻撃難易度が高いです。Silent Refreshと組み合わせると良い戦略になります。

実践推奨:Access TokenはHttpOnly Cookieに、CSRF防御はSameSite=Lax + CSRFトークンの二重適用。Refresh TokenもHttpOnly Cookieに保存し、Pathを/auth/refreshに制限してください。


シリーズ目次

このシリーズは全5編で構成されています。このインデックスページで共通概念を学んだ後、各フレームワーク別の実践実装に移動してください。

タイトル核心内容
0編現在の記事(インデックス)認証フロー全体マップ、共通概念の整理
1編Spring BootSecurityFilterChain、JWTフィルター、CORS設定、CSRF防御
2編DjangoDRF + SimpleJWT、SessionMiddleware、django-cors-headers
3編ReactAxiosインターセプター、Silent Refresh、Protected Route
4編Next.jsMiddleware、Server Actions、SSRクッキー伝達、next-auth
5編統合実践編 — SSO/OIDC + ハイブリッドアーキテクチャKeycloak/Okta連携、BFFパターン、マルチサービスSSO

推奨読書順序:

  1. このインデックス記事で全体フローを把握します。
  2. 自分が使用するバックエンドフレームワーク編(1編または2編)を読みます。
  3. フロントエンド編(3編または4編)を読みます。
  4. 5編で全体を統合するアーキテクチャを学習します。

セキュリティチェックリスト(シリーズ共通)

フレームワークに関係なく、すべての認証システムに適用すべきセキュリティ項目です。デプロイ前に必ず確認してください。

クッキー設定

  • Access TokenクッキーにHttpOnly属性が設定されているか?
  • すべての認証クッキーにSecure属性が設定されているか?
  • SameSite属性がサービスアーキテクチャに合わせて設定されているか?
  • Refresh TokenクッキーのPathが更新エンドポイントに制限されているか?
  • クッキーのMax-Ageが適切な時間に設定されているか?
  • クッキーのDomainが必要な範囲にのみ制限されているか?

トークン管理

  • Access Tokenの有効期限が十分に短いか?(推奨:5〜15分)
  • Refresh Tokenの有効期限が設定されているか?(推奨:7〜30日)
  • Refresh Token Rotationが実装されているか?
  • 窃取されたRefresh Tokenを無効化できるメカニズムがあるか?
  • JWT署名にRS256またはES256非対称アルゴリズムを使用しているか?
  • JWTのissaudexpクレームをすべて検証しているか?

CSRF防御

  • 状態変更リクエスト(POST、PUT、DELETE)でCSRFトークンを検証しているか?
  • CSRFトークンがリクエストごとまたはセッションごとに更新されるか?
  • SameSiteクッキー属性とCSRFトークンを二重に適用しているか?

CORS設定

  • Access-Control-Allow-Originにワイルドカード(*)を使用していないか?
  • Access-Control-Allow-Credentials: trueが設定されているか?
  • 許可するOriginリストがホワイトリストで管理されているか?
  • Preflightリクエスト(OPTIONS)が認証なしで通過するように設定されているか?

転送セキュリティ

  • すべての通信がHTTPSで行われているか?
  • HSTS(HTTP Strict Transport Security)ヘッダーが設定されているか?
  • TLS 1.2以上を使用しているか?

ログアウト

  • ログアウト時にサーバー側のセッション/トークンが無効化されるか?
  • ログアウト時にすべての認証クッキーが削除されるか?
  • SSO環境でSingle Logout(SLO)が実装されているか?

よくあるバグと誤解

誤解1:「JWTは暗号化されている」

JWTは**署名(Signed)されているだけで暗号化(Encrypted)**されていません。Base64URLエンコーディングは暗号化ではありません。誰でもPayloadをデコードして内容を読むことができます。

# JWT Payloadのデコード(誰でも可能)
echo "eyJzdWIiOiJ1c2VyMTIzIn0" | base64 -d
# 出力: {"sub":"user123"}

機密情報(パスワード、社会保障番号など)をJWT Payloadに絶対に入れないでください。本当に暗号化が必要な場合はJWE(JSON Web Encryption)を使用してください。

誤解2:「localStorageにJWTを保存しても大丈夫」

SPAフレームワークの多くのチュートリアルがlocalStorageにJWTを保存する例を示しています。これはXSS攻撃に脆弱です。サードパーティライブラリの1つの脆弱性だけでトークンが窃取される可能性があります。

// 危険なパターン
localStorage.setItem('token', jwt)

// 安全なパターン:HttpOnly Cookieを使用(サーバーで設定)
// フロントエンドはトークンを直接扱わない

誤解3:「SameSite=LaxならCSRF攻撃は完全に防御される」

SameSite=LaxはほとんどのCSRFを防御しますが完全ではありません。クロスサイトのGETリクエストにはクッキーが送信されます。GETリクエストで状態を変更するAPIがある場合、依然として脆弱です。

# GET /api/users/delete?id=123SameSite=Laxでも防御不可!
# → 状態変更は必ずPOST/PUT/DELETEで設計してください

誤解4:「Refresh TokenはAccess Tokenより安全」

Refresh Tokenは長期間有効であるため、窃取されるとAccess Tokenよりさらに危険です。Refresh Tokenが窃取されると、攻撃者が無限に新しいAccess Tokenを発行できます。

防御戦略:

  • Refresh Token Rotation:更新時に以前のトークンを即座に無効化
  • Token Family追跡:すでに使用されたRefresh Tokenが再使用されたら、そのFamily全体を無効化
  • Refresh TokenをHttpOnly Cookieに保存しPath制限

誤解5:「CORSがサーバーを保護してくれる」

CORSはブラウザのポリシーです。curl、Postman、サーバー間通信ではCORSは適用されません。CORSは悪意のあるWebサイトがユーザーのブラウザを利用してAPIを呼び出すことを防ぐだけで、API自体を保護するものではありません。

# CORSに関係なく動作
curl -X POST https://api.myservice.com/data \
  -H "Cookie: access_token=stolen_token"
# → サーバー側のトークン検証が必ず必要です

よくあるバグ:トークン更新のレースコンディション

複数のAPIリクエストが同時に401を受け取ると、Refreshリクエストも複数回発生します。Refresh Token Rotationを使用している場合、最初のリクエストのみ成功し、残りは失敗します。

// 解決:更新リクエストを1つにまとめるパターン
let refreshPromise = null

async function refreshToken() {
  if (refreshPromise) return refreshPromise // すでに更新中なら待機

  refreshPromise = axios.post('/auth/refresh').finally(() => {
    refreshPromise = null
  })

  return refreshPromise
}

参考資料

このシリーズの執筆時に参考にした公式ドキュメントと標準仕様です。認証システムを設計する際に必ず原文を確認してください。

標準仕様(RFC)

  1. RFC 7519 — JSON Web Token (JWT) — JWT標準仕様。クレーム、署名、検証手順を定義
  2. RFC 6749 — OAuth 2.0 Authorization Framework — OAuth 2.0コアフレームワーク。Grant Typeごとのフローを定義
  3. RFC 6750 — OAuth 2.0 Bearer Token Usage — Bearer Token送信方法(Authorizationヘッダー、クエリパラメータなど)
  4. RFC 7517 — JSON Web Key (JWK) — 公開鍵配布のためのJWK Set標準
  5. RFC 6265 — HTTP State Management Mechanism (Cookie) — HTTPクッキー標準仕様

OpenID Connect

  1. OpenID Connect Core 1.0 — OAuth 2.0上に認証(Authentication)レイヤーを追加した標準

セキュリティガイドライン

  1. OWASP — Session Management Cheat Sheet — セッション管理セキュリティのベストプラクティス
  2. OWASP — JSON Web Token Cheat Sheet — JWTセキュリティチェックリスト
  3. OWASP — Cross-Site Request Forgery Prevention — CSRF防御戦略

フレームワーク公式ドキュメント

  1. MDN — HTTP Cookies — クッキー属性(SameSite、HttpOnly、Secureなど)の詳細説明
  2. Spring Security — OAuth 2.0 Resource Server — Spring Boot JWT認証公式ガイド
  3. Django REST Framework — Authentication — DRF認証メカニズム公式ドキュメント
  4. Next.js — Authentication — Next.js App Routerベースの認証実装ガイド
  5. MDN — CORS — Cross-Origin Resource Sharing完全ガイド