シリーズ紹介 — なぜこのシリーズを書くのか
Web認証はシンプルではありません。「ログイン機能を1つ作ればいい」と考えがちですが、実践では以下のような疑問が次々と湧いてきます。
- トークンを**クッキー**に入れるか、**localStorage**に入れるか?
- **JWT**と**セッション**のどちらを選ぶべきか?
- **HttpOnly Cookie**と**CSRFトークン**をどう組み合わせるか?
- **SSO(Single Sign-On)**環境で複数サービス間の認証状態をどう共有するか?
- **Access Token更新**はフロントエンドで行うか、バックエンドで行うか?
- **CORS**と**credentials**設定はなぜいつも苦労するのか?
これらの質問の正解は**フレームワークによって異なります**。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. Code → Token │ │
│ │◀───────────────────│ │
│ │ │ (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 Cookie**:`document.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 Boot](/blog/architecture/2026-03-08-sso-cookie-jwt-auth-spring-boot) | SecurityFilterChain、JWTフィルター、CORS設定、CSRF防御 |
| 2編 | [Django](/blog/architecture/2026-03-08-sso-cookie-jwt-auth-django) | DRF + SimpleJWT、SessionMiddleware、django-cors-headers |
| 3編 | [React](/blog/architecture/2026-03-08-sso-cookie-jwt-auth-react) | Axiosインターセプター、Silent Refresh、Protected Route |
| 4編 | [Next.js](/blog/architecture/2026-03-08-sso-cookie-jwt-auth-nextjs) | Middleware、Server Actions、SSRクッキー伝達、next-auth |
| 5編 | [統合実践編 — SSO/OIDC + ハイブリッドアーキテクチャ](/blog/architecture/2026-03-08-sso-cookie-jwt-auth-hybrid-architecture) | 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の`iss`、`aud`、`exp`クレームをすべて検証しているか?
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=123 ← SameSite=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)](https://datatracker.ietf.org/doc/html/rfc7519) — JWT標準仕様。クレーム、署名、検証手順を定義
2. [RFC 6749 — OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) — OAuth 2.0コアフレームワーク。Grant Typeごとのフローを定義
3. [RFC 6750 — OAuth 2.0 Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750) — Bearer Token送信方法(Authorizationヘッダー、クエリパラメータなど)
4. [RFC 7517 — JSON Web Key (JWK)](https://datatracker.ietf.org/doc/html/rfc7517) — 公開鍵配布のためのJWK Set標準
5. [RFC 6265 — HTTP State Management Mechanism (Cookie)](https://datatracker.ietf.org/doc/html/rfc6265) — HTTPクッキー標準仕様
OpenID Connect
6. [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) — OAuth 2.0上に認証(Authentication)レイヤーを追加した標準
セキュリティガイドライン
7. [OWASP — Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html) — セッション管理セキュリティのベストプラクティス
8. [OWASP — JSON Web Token Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html) — JWTセキュリティチェックリスト
9. [OWASP — Cross-Site Request Forgery Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) — CSRF防御戦略
フレームワーク公式ドキュメント
10. [MDN — HTTP Cookies](https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies) — クッキー属性(SameSite、HttpOnly、Secureなど)の詳細説明
11. [Spring Security — OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html) — Spring Boot JWT認証公式ガイド
12. [Django REST Framework — Authentication](https://www.django-rest-framework.org/api-guide/authentication/) — DRF認証メカニズム公式ドキュメント
13. [Next.js — Authentication](https://nextjs.org/docs/app/building-your-application/authentication) — Next.js App Routerベースの認証実装ガイド
14. [MDN — CORS](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS) — Cross-Origin Resource Sharing完全ガイド
현재 단락 (1/196)
Web認証はシンプルではありません。「ログイン機能を1つ作ればいい」と考えがちですが、実践では以下のような疑問が次々と湧いてきます。