JWT のステートレス性とは
JWTの強みは、署名検証だけでトークンの正当性を確認できることです。
APIサーバは、毎回セッションストアに問い合わせなくても、JWTの署名と有効期限を見て認証できます。
JWT -> 署名検証 -> 有効期限確認 -> OK
これをステートレス認証と呼ぶことがあります。
なぜ便利なのか
複数のAPIサーバやマイクロサービスがある場合、各サービスが同じ署名鍵や公開鍵でJWTを検証できます。
Client -> Service A
-> Service B
-> Service C
各サービスが中央のセッションストアに問い合わせずに認証できるため、スケールしやすく見えます。
ログアウトで問題が出る
問題は、ログアウトです。
ユーザーがログアウトしたとします。サーバは「このJWTをもう使えないようにしたい」と考えます。
しかし、純粋なJWT方式では、サーバは発行済みJWTを覚えていません。JWTは署名された文字列としてクライアント側にあります。
有効期限が切れるまでは、盗まれたJWTが使われる可能性があります。
注意: JWT は発行済みトークンを忘れることでスケールしやすくなります。しかし、忘れているからこそ特定トークンの即時失効が難しくなります。
対策1: 有効期限を短くする
アクセストークンの有効期限を短くします。
{
"sub": "user_001",
"exp": 1760000900
}
たとえば15分で期限切れにすれば、盗まれても使える時間を短くできます。
ただし、ユーザー体験のためには更新処理が必要になります。そこで refresh token が使われます。
対策2: ブラックリスト
ログアウトされたJWTのIDを、Redisなどに保存しておく方法です。
JWT検証
-> 署名確認
-> exp確認
-> jti がブラックリストにないか確認
JWTには jti という一意IDを入れることがあります。
{
"sub": "user_001",
"jti": "token_abc123",
"exp": 1760000900
}
ログアウト時に token_abc123 をRedisへ保存し、API呼び出し時に照合します。
この方法は有効ですが、毎回ストアを見るため、ステートレス性は弱まります。
対策3: tokenVersion
ユーザーレコードに tokenVersion を持たせる方法もあります。
{
"userId": "user_001",
"tokenVersion": 3
}
JWTにも同じ値を入れます。
{
"sub": "user_001",
"tokenVersion": 3
}
強制ログアウトしたいときは、DBの tokenVersion を4にします。古いJWTは tokenVersion=3 なので無効になります。
ただし、検証時にDBを見る必要があります。これも純粋なステートレスではありません。
対策4: DBセッションに戻す
即時失効が重要なら、最初からDBセッションやRedisセッションを使う方が素直です。
| 要件 | 向いている方式 |
|---|---|
| 即時ログアウトが重要 | DB / Redis セッション |
| 管理者の強制ログアウトが必要 | DB / Redis セッション |
| サーバ間で軽く検証したい | JWT |
| 短命なAPIアクセス | JWT |
金融、医療、管理画面などでは、即時失効の要求が強くなりやすいため、DBセッションが合う場面も多いです。
ステートレスのジレンマ
JWTを純粋にステートレスに使うと、即時失効が難しくなります。
即時失効を厳密にしようとすると、RedisやDBを見る必要が出ます。
純粋JWT
-> 速い、共有ストア不要
-> 失効が難しい
JWT + ブラックリスト
-> 失効しやすい
-> 結局ストアを見る
これがJWTのステートレス性とログアウトのジレンマです。
まとめ
JWTは、署名検証だけで認証できるため便利です。しかし、その便利さは即時失効の難しさと表裏一体です。
- JWTは発行済みトークンをサーバが覚えない設計にできる
- 覚えないからスケールしやすい
- 覚えないからログアウト後の即時失効が難しい
- 短い有効期限、ブラックリスト、tokenVersionなどの対策がある
- 厳密な失効が必要ならDBセッションも検討する
参考リソース
- 公式ドキュメント - JWT のステートレス性と即時ログアウト問題 を確認するための一次情報