はじめに
認証はすべてのWebアプリケーションの根幹です。ステートレスなJWT(JSON Web Token)認証とステートフルなセッション型認証は、どちらも「リクエストのたびに誰であるかを確認する」という目的を果たしますが、ストレージ方式、失効(revocation)、セキュリティ特性において根本的に異なります。本記事では両者を詳細に比較し、アプリケーションに適した方式を選択するための指針を提供します。
JWTの構造
JWTはbase64urlエンコードされた3つのセグメントをドットで連結した自己完結型のトークンです。
header.payload.signature
// ヘッダー
{ "alg": "HS256", "typ": "JWT" }
// ペイロード
{ "sub": "user123", "iat": 1712345678, "exp": 1712349278, "role": "admin" }
| セグメント | 内容 |
|---|---|
| Header | アルゴリズム(HS256, RS256)とトークン種別 |
| Payload | クレーム — sub(subject)、iat(発行時刻)、exp(有効期限)、カスタムデータ |
| Signature | 改ざん検知のための署名 |
ペイロードはbase64エンコードされているだけで暗号化はされていません。機密情報をペイロードに含めてはいけません(JWEを使用する場合を除く)。
セッション型認証
セッション型認証では、サーバー側がセッションデータ(メモリ、Redis、データベース)を保持し、クライアントはセッションIDのみをCookieに保持します。
Client → Server: POST /login (credentials)
Server → Server: Create session (DB/Redisにデータ保存)
Server → Client: Set-Cookie: session_id=random_hash
Client → Server: GET /protected (Cookie付与)
Server → Server: セッションストアを検索
Server → Client: 200 OK
比較表
| 項目 | JWT | セッション |
|---|---|---|
| データ保存場所 | クライアント側 | サーバー側 |
| サーバー状態 | ステートレス — DB不要 | ステートフル — ストア問い合わせ必須 |
| スケーラビリティ | 高い — 共有ストア不要 | Redis/DBの一元管理が必要 |
| 失効(revocation) | 困難 — expまで有効 | 即時 — セッション削除 |
| トークンサイズ | 大(数百バイト) | 小(セッションID ~32バイト) |
| XSS耐性 | JSからアクセス可能(localStorage) | httpOnly CookieでJSから不可 |
| CSRF対策 | 手動対応が必要 | SameSite Cookie + CSRFトークン |
JWTの失効問題
JWTの最大の弱点は**revocation(失効)**です。一度発行されたJWTはexpクレームに指定された時刻まで有効であり、ログアウト、パスワード変更、アカウント停止をしても即座に無効化できません。
対策パターン
1. 短期間アクセストークン + リフレッシュトークン:
// アクセストークン: 15分
// リフレッシュトークン: 7日(DB保存)
アクセストークンの有効期間を短くすることで、漏洩時の被害を局限します。リフレッシュトークンはサーバー側で管理されるため失効可能です。
2. トークンブラックリスト:
// ログアウト時にjti(JWT ID)をRedisに追加
await redis.set(`blacklist:${jti}`, 'true', 'EX', remainingTTL);
サーバー状態を部分的に導入しますが、全セッションではなく失効トークンのみを管理します。
3. 署名鍵の変更:
署名鍵を変更すると全トークンが無効化されます。緊急時の最終手段です。
XSSとCSRF
| 脅威 | JWT(localStorage) | セッション(httpOnly Cookie) |
|---|---|---|
| XSS | localStorageからトークンが漏洩 | JSからトークンにアクセス不可 |
| CSRF | 影響なし(手動でAuthorizationヘッダ) | SameSiteなしでは脆弱 |
JWTをlocalStorageに保存すると、XSS攻撃によりトークンが容易に盗まれます。httpOnly Cookieはこの問題を防ぎますが、Cookieは自動送信されるためCSRF対策が必要になります。
JWTのベストプラクティス: アクセストークンはhttpOnly, Secure, SameSite=StrictのCookieに保存し、サーバーはAuthorizationヘッダから読み取る(Cookieからは読まない)ことでCSRFを防止します。
res.cookie('token', jwt, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 15 * 60 * 1000
});
ハイブリッドアプローチ
多くのプロダクションシステムは両者を組み合わせています。
| 方式 | アクセストークン | リフレッシュトークン |
|---|---|---|
| JWTのみ | JWT(メモリ保持) | JWT(長期exp) |
| セッション型 | 不透明なセッションID | — |
| ハイブリッド | 短期JWT(15分) | 不透明なリフレッシュトークン(DB保存) |
ハイブリッドモデルは、大部分のリクエストでJWTのステートレス効率を活かしつつ、リフレッシュトークンによる失効機能を保持します。
選択の指針
| 優先事項 | 推奨方式 |
|---|---|
| サーバーDB負荷の最小化 | JWT(ステートレス) |
| 即時ログアウト・アカウント停止 | セッション型 |
| マイクロサービス / APIゲートウェイ | JWT(パススルー) |
| モバイルアプリ | JWT(Cookie非依存) |
| サーバーレンダリングWebアプリ | セッション(httpOnly Cookie) |
| サードパーティAPI公開 | JWT(Bearerトークン) |
まとめ
JWTとセッション型認証に絶対的な優劣はありません。JWTは分散システム、モバイルアプリ、APIファーストアーキテクチャで威力を発揮します。セッション型認証はログアウト、失効、サーバー側セキュリティ制御において優れています。短期JWT + 失効可能なリフレッシュトークンのハイブリッドパターンは、両方の長所を活かし弱点を補う実用的な妥協策です。
