「リフレッシュトークンは一生モノ」という幻想を捨てろ:OAuth 2.0 ローテーションの深淵
現場でコードを叩いている諸君、ご苦労。セキュリティの現場に長くいると、「OAuth 2.0はもう完璧に実装した」と胸を張るエンジニアに出くわすが、その実、リフレッシュトークンの管理がザルであるケースが後を絶たない。
リフレッシュトークンをただの「アクセストークンの交換券」としか見ていないなら、それは今日で終わりにしよう。攻撃者は、君たちが捨てたログや、端末のキャッシュ、あるいは中間者攻撃で奪ったリフレッシュトークンを、永遠に使える「マスターキー」として虎視眈々と狙っている。
今回は、OAuth 2.0 における「リフレッシュトークン・ローテーション」という、実戦で必須の防御策を叩き込む。
—
なぜ「ローテーション」が不可欠なのか?(攻撃者の視点)
攻撃者がリフレッシュトークンを奪取したとき、最も恐ろしいのは「追跡不能なセッション継続」だ。
1. 窃取: XSSや、端末のストレージ(ローカルストレージへの不用意な保存など)からトークンを抜き取る。
2. 永続化: 有効期限が切れるまで、攻撃者はバックグラウンドで新しいアクセストークンを生成し続ける。
3. 無検知: 本来のユーザーがログアウトしても、攻撃者のセッションは生きている。
ここで「リフレッシュトークン・ローテーション」を導入すると、「リフレッシュトークンを使用するたびに、古いトークンを無効化し、新しいペアを発行する」という挙動になる。もし攻撃者が奪ったトークンを先に使ってしまえば、本物のユーザーが次に更新しようとした瞬間に「リフレッシュトークンの再利用」が検知され、即座にそのセッションを無効化して不審なアクセスを遮断できるというわけだ。
—
実践:セキュアなリフレッシュトークン・ローテーションの実装(Python/Flask例)
概念はシンプルだが、実装には「競合状態(Race Condition)」への配慮が必要だ。ネットワークの遅延でリクエストが重複した場合、片方が先に無効化されてしまうからだ。
以下は、Redisをバックエンドに想定した、安全なローテーションのロジックだ。
import uuid
from flask import request, jsonify
疑似的なDB/Redis管理クラス
class TokenManager:
def rotate_token(self, old_refresh_token, user_id):
# 1. トークンが「使用済み」かつ「最近のもの」か検証
# 2. データベースでトランザクションを開始
# 検証:このトークンは過去に使用されていないか?
if not self.is_valid(old_refresh_token):
# 既に使われているなら、それは攻撃の可能性がある
self.revoke_all_sessions_for_user(user_id)
raise Exception(“セキュリティ違反:リフレッシュトークンの再利用を検知”)
# 新しいトークンを発行
new_refresh_token = str(uuid.uuid4())
# 古いものを無効化し、新しいものを保存
self.mark_as_used(old_refresh_token)
self.save_new_token(new_refresh_token, user_id)
return new_refresh_token
運用上のポイント:
競合が発生した場合の「猶予期間(Grace Period)」を数秒設けるのがコツだ。
ネットワークエラーで再送された場合、直前のトークンなら許可するような設計にする。
—
現場のエンジニアが守るべき3つの鉄則
コードを書く前に、以下の運用ルールをチームに共有してほしい。
1. リフレッシュトークンの保存場所に細心の注意を
ブラウザの `localStorage` は絶対NGだ。XSSで一撃で抜かれる。`HttpOnly` かつ `Secure` 属性を付与したCookieに保存し、CSRF対策を万全にすること。これが大前提だ。
2. トークン再利用時の即時無効化(Revocation Chain)
万が一、同一のリフレッシュトークンが二度使われた場合、それは攻撃者が奪取・利用した証拠だ。そのユーザーIDに紐づくすべてのセッションを強制的に全ログアウトさせるのが、最も確実な防御だ。ユーザーには「不審なアクセスを検知したため再ログインしてください」と通知すればいい。
3. トークンの有効期限は極力短く
リフレッシュトークンであっても、半年や1年も有効にする必要はない。ビジネス要件と相談し、例えば「7日間」程度に絞り込み、その期間内であればローテーションでセッションが伸びるという設計にするのが、被害範囲を最小化する極意だ。
—
まとめ:セキュリティは「性悪説」から始まる
「ローテーションの実装は面倒だ」「パフォーマンスに影響が出るのでは?」と思うかもしれない。だが、一度大規模なトークン漏洩インシデントの事後対応を経験すれば、そんな甘い考えは消え去るはずだ。
セキュリティは、完璧な壁を作ることではなく、「侵入された後の被害をどうやって最小限に食い止めるか」という設計思想の積み重ねだ。
今回のローテーション実装は、そのための強力な武器になる。まずは君のプロジェクトのOAuth認可サーバーの実装を確認してくれ。もし「リフレッシュトークンが使い回されている」なら、それが今日君が修正すべき最優先のバグだ。
現場からは以上だ。また次の戦場で会おう。

コメント