- Authors

- Name
- Youngju Kim
- @fjvbn20031
シリーズ紹介 — なぜこのシリーズを書くのか
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 | SecurityFilterChain、JWTフィルター、CORS設定、CSRF防御 |
| 2編 | Django | DRF + SimpleJWT、SessionMiddleware、django-cors-headers |
| 3編 | React | Axiosインターセプター、Silent Refresh、Protected Route |
| 4編 | Next.js | Middleware、Server Actions、SSRクッキー伝達、next-auth |
| 5編 | 統合実践編 — SSO/OIDC + ハイブリッドアーキテクチャ | Keycloak/Okta連携、BFFパターン、マルチサービスSSO |
推奨読書順序:
- このインデックス記事で全体フローを把握します。
- 自分が使用するバックエンドフレームワーク編(1編または2編)を読みます。
- フロントエンド編(3編または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)
- RFC 7519 — JSON Web Token (JWT) — JWT標準仕様。クレーム、署名、検証手順を定義
- RFC 6749 — OAuth 2.0 Authorization Framework — OAuth 2.0コアフレームワーク。Grant Typeごとのフローを定義
- RFC 6750 — OAuth 2.0 Bearer Token Usage — Bearer Token送信方法(Authorizationヘッダー、クエリパラメータなど)
- RFC 7517 — JSON Web Key (JWK) — 公開鍵配布のためのJWK Set標準
- RFC 6265 — HTTP State Management Mechanism (Cookie) — HTTPクッキー標準仕様
OpenID Connect
- OpenID Connect Core 1.0 — OAuth 2.0上に認証(Authentication)レイヤーを追加した標準
セキュリティガイドライン
- OWASP — Session Management Cheat Sheet — セッション管理セキュリティのベストプラクティス
- OWASP — JSON Web Token Cheat Sheet — JWTセキュリティチェックリスト
- OWASP — Cross-Site Request Forgery Prevention — CSRF防御戦略
フレームワーク公式ドキュメント
- MDN — HTTP Cookies — クッキー属性(SameSite、HttpOnly、Secureなど)の詳細説明
- Spring Security — OAuth 2.0 Resource Server — Spring Boot JWT認証公式ガイド
- Django REST Framework — Authentication — DRF認証メカニズム公式ドキュメント
- Next.js — Authentication — Next.js App Routerベースの認証実装ガイド
- MDN — CORS — Cross-Origin Resource Sharing完全ガイド