🎓 レベル:標準 | 重要度:A(必須) 📎 前提:認証の基礎(パスワード保存とMFA)
要点(BLUF)
- HTTP はステートレスなので、一度ログインした状態を毎回運ぶ仕組みが要る。方式は大きくセッション(サーバ保持)とトークン(自己完結)。
- JWT は署名つきの自己完結トークン。サーバに状態を持たなくても、署名検証で「改ざんされていない・正規発行」を確かめられる。
- 守る側の要点は、(1) 署名を必ず検証し許可アルゴリズムを固定、(2) 有効期限を短く、(3) 保管場所(Cookie 属性)を安全にすること。
概念:ログイン状態をどう運ぶか
ログインは一瞬ですが、その後のリクエストでも「この人は認証済み」と分からねばなりません。HTTP 自体は状態を覚えないので、状態を運ぶ仕組みが必要です。2方式あります。
- セッション方式:サーバが状態(誰がログイン中か)を保持し、クライアントにはランダムなセッション ID だけを渡す。ID は中身を持たない「預かり証」。
- トークン方式(JWT 等):状態をクライアント側のトークンに入れて署名する。サーバは状態を持たず、署名検証だけで信頼する(ステートレス)。
仕組み:セッション vs JWT
| 観点 | セッション(サーバ保持) | JWT(自己完結) |
|---|---|---|
| 状態の所在 | サーバ(ストア) | トークン内(クライアント) |
| 検証方法 | ストア照合 | 署名検証 |
| 失効 | 容易(サーバで消す) | 難しい(期限切れ待ち+失効リスト) |
| スケール | ストア共有が要る | 状態共有不要で水平展開しやすい |
| 主な注意点 | ID 漏えい・固定化 | 署名検証・保管・失効 |
JWT は3部構成(ヘッダ.ペイロード.署名)で、ヘッダとペイロードはBase64 でエンコードされているだけ=暗号化ではない点が重要です。誰でも中身を読めます。守っているのは機密性ではなく完全性と真正性(署名、デジタル署名と証明書(PKI))です。だから JWT に秘密情報を入れてはいけません。
flowchart LR
LOGIN["ログイン成功"] --> ISSUE["サーバがJWTを発行(署名つき)"]
ISSUE --> STORE["クライアントが保管(HttpOnly Cookie 推奨)"]
STORE -->|"以降のリクエストに添付"| VERIFY{"サーバが署名と期限を検証"}
VERIFY -->|"正当"| ALLOW["処理を許可"]
VERIFY -->|"改ざん・期限切れ・alg不正"| DENY["拒否"]
防御側の使い方/設定
- 署名を必ず検証し、許可アルゴリズムを固定:検証側で
algorithms=["HS256"]のように明示し、alg=noneやアルゴリズム混同(公開鍵を HMAC 鍵と誤用させる類)を拒否する。 - 有効期限(exp)を短く:アクセストークンは短命に。長期はリフレッシュトークン側で管理し、こちらは厳重保管・失効可能に。
- Cookie 属性を固める:
HttpOnly(JS から読めない=XSS 対策)、Secure(HTTPS のみ)、SameSite(CSRF 緩和)。 - セッション方式では固定化対策:ログイン成功時にセッション ID を再生成し、推測不能な十分長い乱数を使う。
- ログアウト・失効の設計:JWT は失効しにくいので、重要操作は短命トークン+サーバ側の失効リスト併用を検討。
なぜ安全か:署名が「改ざんできない預かり証」を作る
JWT が状態をクライアントに預けても安全なのは、署名により中身を1文字でも変えると検証が失敗するからです(完全性)。さらに署名鍵を持つのは発行サーバだけなので、第三者は正規トークンを作れません(真正性)。ただしこれは「検証側が必ず署名を確かめ、許可アルゴリズムを固定している」ことが前提です。検証を省く・alg=none を受理する実装は、この保証を自ら捨てています。
security-study/labs/jwt_verify_demo.py の実行結果(抜粋):
=== 1. 正規トークンは検証できる ===
claims = {'sub': 'user-123', 'role': 'viewer', 'exp': 1782636475}
=== 2. ペイロード改ざん -> 署名検証で拒否 ===
改ざん拒否 OK -> InvalidSignatureError
=== 3. 期限切れトークン -> 拒否 ===
期限切れ拒否 OK -> ExpiredSignatureError
改ざんは InvalidSignatureError、期限切れは ExpiredSignatureError で確実に拒否されます。
仕組みの直観
セッションはクロークの預かり札です。札(セッション ID)自体に意味はなく、番号を見せると店が控え(サーバの状態)と照合して荷物を返す。JWT は改ざん防止フィルム付きの会員証で、券面に会員情報が印刷され、偽造防止の封(署名)が押してある。店は控えを持たず、封が本物かだけ確かめます。封の確認をサボれば偽造証を通してしまいます。
⚠️ よくある誤解・設定ミス
- JWT を暗号化と勘違い:ペイロードは誰でも読める。秘密情報を入れない。
- 署名検証を省く/
alg=noneを受理:致命的。許可アルゴリズムを固定して拒否する。 - 有効期限が長すぎる/無期限:盗まれたトークンが使われ続ける。短命+失効設計に。
- トークンを
localStorageに置く:XSS で読まれうる。HttpOnlyCookie が安全寄り(認証とセッションの安全な実装)。 - ログイン後にセッション ID を再生成しない:セッション固定化に弱い。
対応 lab
security-study/labs/jwt_verify_demo.py— JWT の署名検証、改ざん・期限切れの拒否、許可 alg の固定
関連
- トークンの署名・検証の土台 → デジタル署名と証明書(PKI)
- 第三者への権限委譲 → OAuth 2.0 と OpenID Connect
- Cookie・XSS/CSRF の実装防御 → 認証とセッションの安全な実装