セキュリティ

リフレッシュトークンとは?更新フローと寿命・ローテーション設計を実装者向けに解説

AIの活用によるセキュリティの向上

アクセストークンの寿命を15分に縮めた途端、15分ごとにログイン画面へ飛ばされる。この矛盾を解くために置かれるのがリフレッシュトークンです。RFC 6749が定めるこの資格情報は、リソースサーバへは一切送らず、トークンエンドポイントに対してだけ提示して新しいアクセストークンを受け取ります。本記事では、grant_type=refresh_tokenによる更新リクエストの組み立て方、画面初期化時の並行リクエストで壊れる箇所、RFC 9700(BCP 240)が公開クライアントへ課すローテーションと送信者制約、RFC 7009にもとづく失効設計、そして寿命の決め方までを実装の順序で整理します。

まとめ:リフレッシュトークンの適用条件と寿命・失効設計の結論

結論から置きます。リフレッシュトークンは「アクセストークンを短命にしても再ログインを強いない」ための仕組みであって、認証状態そのものを表すものではありません。導入すべきなのは、外部クライアントへ権限を渡す場合、クッキーが使えないネイティブアプリやCLIの場合、複数APIをまたいで同じ認可を使い回す場合の3つに限られます。フロントとAPIが同一オリジンで完結するWebアプリなら、セッションクッキーのほうが失効管理は簡潔に済みます。

実装で手が止まるのは発行処理ではありません。更新のたびに新しい値へ差し替えるローテーションを入れると、画面初期化で複数のAPIが同時に401を受けた瞬間、片方が無効になった古い値を掴んで落ちる。ここはクライアント側で更新処理を1本に集約し、認可サーバ側に数十秒の再利用猶予を持たせて解きます。

もう一点、失効テーブルを持たない構成は事故になります。退職者や退会ユーザーのリフレッシュトークンを止める手段がなくなるためです。発行済みの系列をDBで管理し、RFC 7009のrevocationエンドポイントとログアウト処理を接続するところまでが最小構成になります。

リフレッシュトークンの定義とアクセストークンを短命に保つ仕組み

まず、何を持ち、どこへ送る資格情報なのかを他のトークンと切り分けます。

RFC 6749が定める発行主体と提示先を限定する原則の実装意図

リフレッシュトークンは、認可サーバが発行してクライアントへ渡す資格情報です。RFC 6749(2012年10月発行)の1.5節は、その用途を「アクセストークンが無効になった、あるいは期限切れになったときに、新しいアクセストークンを取得するための資格情報」と定義しています。アクセストークンと決定的に違うのは提示先で、リソースサーバへ送ることはありません。送り先は認可サーバのトークンエンドポイントだけです。

この限定が効きます。APIリクエストのたびにネットワークへ流れるのは寿命15分のアクセストークンだけになり、数週間有効な資格情報はトークン更新時にしか露出しない。仮にプロキシのログにアクセストークンが残っても、被害は残り寿命の範囲で頭打ちになります。OAuth 2.0のグラント種別全体の位置づけはOAuth 2.0の認可フローと認証・認可の違いの整理を先に読むと把握しやすくなります。

アクセストークン・IDトークンとの寿命と失効の効き方の実務的な違い

3種のトークンは名前が似ているだけで、提示先も止め方も別物です。設計時に取り違えると、失効させたはずのトークンが動き続けます。

種別 提示先 代表的な寿命 失効の効き方
アクセストークン リソースサーバ 5分から60分 JWT型は即時に止まらない
IDトークン クライアント本体 数分から1時間 再認証で置き換える
リフレッシュトークン トークンエンドポイント 数日から数か月 revocationで即時に停止

注意したいのは1行目です。自己完結型のJWTをアクセストークンに使う構成では、サーバ側に状態を持たないぶん、発行後に取り消しても期限が来るまで検証は通ってしまいます。だからこそ寿命を5分から15分へ縮め、その短さをリフレッシュトークンで埋め合わせる。トークン形式そのものの判断材料はJWTの構造と署名検証の仕組みにまとめています。

offline_accessスコープとリフレッシュトークンが返らない構成

「レスポンスにrefresh_tokenが入っていない」という相談の大半は、不具合ではなく設定どおりの挙動です。OpenID Connect Core 1.0は、ユーザーが画面を離れた後もアクセストークンを取り直す権限を要求するスコープとしてoffline_accessを定めており、認可サーバによっては同意画面の明示的な再表示を条件に付けます。要求スコープに入れ忘れたまま更新処理だけ実装しても、返ってくるのはアクセストークンだけです。

クライアント種別の設定も確認してください。SPAやモバイルアプリとして登録した公開クライアントに対し、リフレッシュトークンの発行を既定で無効にしている認可サーバは珍しくありません。切り分けの順序は、要求スコープ、クライアント種別、認可サーバ側の発行ポリシーの3点です。この順で見れば、たいてい2手目までに原因へ届きます。

refresh_tokenグラントの実装手順と並行リクエストで壊れる箇所

更新処理そのものは数十行で書けます。落ちるのは、同じ処理が同時に2回走ったときです。

更新リクエストの組み立てと応答で確認すべき4項目および判定手順

RFC 6749の6節が定める更新の手順は次のとおりです。

  1. トークンエンドポイントへPOSTし、grant_type=refresh_tokenと保持しているリフレッシュトークンを送る
  2. 機密クライアントはあわせてクライアント認証を行う(公開クライアントは認証情報を持たないため省く)
  3. スコープを指定する場合、元の認可で与えられた範囲を超えない値にする(拡大は仕様上できない)
  4. 応答のexpires_inと、新しいリフレッシュトークンが同梱されているかを確認する

4項目めの確認を落とすと後段で詰まります。ローテーションを有効にした認可サーバは応答に新しい値を含めて返し、その瞬間に古い値を無効化するためです。受け取った新しい値で保存領域を上書きし、旧値は即座に破棄してください。

並行リクエストによる二重更新を再利用猶予で回避する実装の手順

実運用でまず踏むのがここです。ダッシュボード画面の初期化で5本のAPIを同時に呼ぶと、5本が同時に401を受け、5本が同時に更新リクエストを投げる。ローテーションが有効なら、最初の1本だけが成功し、残り4本は無効化済みの値を提示したことになります。認可サーバが再利用検知を実装していれば、正規の利用者が侵害とみなされて全トークンを失効させられます。

クライアント側の対処は単純で、更新処理を1つの非同期処理として共有し、更新中に発生した要求はその完了を待たせるキューへ入れます。サーバ側は同一トークンの再提示を10秒から30秒程度だけ許容する猶予を設けておく。この猶予を数分まで広げると盗まれたトークンの検知窓が消えるため、数十秒で止めるのが現実的な落としどころです。

invalid_grantと401の切り分けおよび再ログイン誘導の設計

更新に失敗したとき、リトライしてよい失敗と、してはいけない失敗があります。リソースサーバが返す401はアクセストークンの期限切れで、更新すれば回復します。トークンエンドポイントが返す400のinvalid_grantは、リフレッシュトークンの期限切れ、ローテーション済み、失効済みのいずれかで、何度投げても結果は変わりません。ここでリトライループを組むと、再利用検知に引っかかって被害を広げます。

403は権限不足であり、トークンの鮮度とは無関係です。切り分けの実装は、401なら1回だけ更新して元のリクエストを再送、invalid_grantなら保存済みトークンを消してログイン画面へ、403はそのままエラー表示、という3分岐で足ります。監視のために、失効理由を認可サーバ側のログへ残す設定も入れておきましょう。

RFC 9700が公開クライアントへ課すローテーションと送信者制約

2025年に発行されたセキュリティBCPで、公開クライアントの扱いは選択肢から要件へ変わりました。

公開クライアントに課される2択とRFC 9700が示すその根拠

RFC 9700「Best Current Practice for OAuth 2.0 Security」(BCP 240・2025年1月発行)は2.2.2節で、公開クライアント向けのリフレッシュトークンについて、送信者制約とするか、ローテーションを用いるかのいずれかをMUSTとして規定しています。SPA、モバイルアプリ、デスクトップアプリはいずれも公開クライアントに該当し、この2択から逃れられません。

機密クライアントが対象外なのは、同文書が示すとおり、リフレッシュトークンを使えるのが発行先のクライアントに限られるためです。クライアント認証を通らなければトークンエンドポイントで弾かれる。サーバサイドで秘密情報を保持できる構成なら、まずクライアント認証を確実に効かせることが先決になります。

再利用検知でトークン系列ごと失効させるデータ設計と実務判定条件

ローテーションは、更新のたびに新しいリフレッシュトークンを発行し、直前の値を無効化する方式です。効果を生むのは無効化そのものではなく、無効化済みの値が再び提示されたときの扱いにあります。正規のクライアントは新しい値へ差し替え済みなので、古い値の提示はコピーが存在する証拠になる。ここで同じ認可グラントから派生した系列を丸ごと失効させます。

DBの持ち方は、系列を識別するIDと世代番号、失効フラグ、有効期限の4列を最小構成にしてください。トークン値そのものは平文で保存せず、ハッシュ化して照合します。系列単位で持てば、再利用を検知した際のクエリが「同一系列の全レコードを失効」の1文で済み、取りこぼしが起きません。

mTLSとDPoPのどちらで送信者制約を実現するかの判断基準

送信者制約は、トークンを特定の送信者に紐づけて、盗んだだけでは使えなくする手法です。選択肢は2つあります。RFC 8705(2020年2月発行)はクライアント証明書とトークンを結びつけ、RFC 9449のDPoP(2023年8月発行)はクライアントが生成した公開鍵にトークンを結びつけます。

判断基準は証明書の配布と失効を運用できるかどうか。サーバ間連携でクライアント数が限られ、証明書のライフサイクル管理をすでに回している組織ならmTLSが素直です。ブラウザやモバイルのように配布先を制御できない場合はDPoPか、実装コストを抑えるならローテーションを選びます。3つを同時に入れる必要はありません。

保管先の選定とRFC 7009にもとづく失効処理の実装範囲の線引き

寿命が長い資格情報である以上、どこに置き、どう止めるかが被害範囲を決めます。

クライアント種別ごとの保管先と漏えい時に広がる被害範囲の違い

置き場所は種別で決まります。迷う余地があるのはSPAだけです。

クライアント種別 保管先 漏えい時の被害範囲
サーバサイドWeb サーバ側のストア サーバ侵害時に限られる
ネイティブアプリ OS標準の資格情報領域 端末のroot化が前提
SPA メモリのみ(永続化しない) XSS一発で持ち出される

SPAでリフレッシュトークンをlocalStorageへ入れる構成は避けてください。XSSが1つ通れば攻撃者の環境から任意のタイミングでアクセストークンを再発行でき、寿命を短くした意味が消えます。長寿命のリフレッシュトークンをどうしても必要とするなら、サーバ側へ閉じ込めるBFF構成へ切り替える判断になります。切替の条件はPKCEとBFFへ切り替える判断ラインで3つに分解して整理しました。

revocationエンドポイントと連鎖失効でカバーできる範囲

RFC 7009「OAuth 2.0 Token Revocation」(2013年8月発行)は、クライアントが自らトークンを失効させるためのエンドポイントを定めています。送るのは失効対象の値と、種別を伝えるtoken_type_hintの2つ。ヒントの初期値はアクセストークンとリフレッシュトークンの2種です。

連鎖の範囲も規定されています。同文書の2.1節は、リフレッシュトークンを失効させたとき、認可サーバは同じ認可グラントにもとづいて発行したアクセストークンもあわせて無効化すべき(SHOULD)としています。ただし自己完結型JWTのアクセストークンは、この無効化がリソースサーバ側の検証に届きません。ログアウト直後の数分間はAPIが通り続ける前提で、アクセストークンの寿命を5分から15分に抑えて辻褄を合わせます。失効を呼ぶべき契機は、ログアウト、パスワード変更、権限の剥奪、端末紛失の申告の4つ。セッションIDを併用する構成での止め方はセッションIDの発行から失効までの実装と突き合わせて設計してください。

寿命設計を決める3入力とリフレッシュトークンを使わない条件の整理

ここからは仕様が答えを持たない領域です。条件を置いて言い切ります。

寿命を決める3つの入力と業務種別ごとに現実的な期限設定の目安

期限の議論は「数字をいくつにするか」から入ると決まりません。入力は3つです。扱うデータの機微度、再ログインを求めてよい頻度、そして監査要件が定めるセッション最大時間。この3つが決まれば数字は自動的に絞られます。

目安を置きます。社内の参照系ツールならアクセストークン60分・リフレッシュトークン30日で運用に耐えます。一般的な業務SaaSはアクセストークン15分・リフレッシュトークン14日あたり。決済や権限変更を含む画面はアクセストークン5分・リフレッシュトークン数時間まで詰め、操作の直前に再認証を挟みます。あわせて、更新のたびに期限を延ばすスライディング方式を採る場合でも、認可の起点から測る絶対上限を別に置いてください。上限がないと、1度ログインしたセッションが永久に延命されます。

リフレッシュトークンを導入すべきでない構成と代替に置く方式の条件

ここは言い切ります。フロントエンドとAPIが同一オリジンで完結し、外部クライアントへ権限を配らないWebアプリに、リフレッシュトークンは要りません。HttpOnly・Secure・SameSiteを付けたセッションクッキーで同じ体験が作れて、失効はサーバ側のセッションストアを消すだけで完結します。そこへトークン方式を重ねると、セッションとトークンで失効経路が二重になり、ログアウト時にどちらかが残る事故を自分で作り込むことになります。

導入する条件は3つに絞れます。第一に、サードパーティを含む外部クライアントへ権限を委譲する場合。第二に、ネイティブアプリやCLIのようにクッキーを前提にできない場合。第三に、複数のAPIやマイクロサービスをまたいで同一の認可を使い回す場合です。いずれにも当てはまらないなら、セッションクッキーのまま踏みとどまってください。

事故になる失敗パターンは無期限発行と失効テーブル未実装の2つ

実装レビューで見つかると差し戻す構成が2つあります。1つは無期限のリフレッシュトークンを発行し、発行済み一覧をDBに持たない構成。退職者や退会ユーザーのトークンが生き続け、止める手段が物理的に存在しません。もう1つは、ローテーションだけ入れて再利用検知を省いた構成です。古い値の再提示を黙って拒否するだけでは、コピーが出回っている事実に気づけないままになります。

どちらも、系列単位の失効テーブルと再利用時のアラートを入れれば塞げます。会員基盤を自前で持ち、認証と権限管理の実装まで含めて相談したい場合は会員管理システム開発で、要件整理から設計・実装まで対応しています。

よくある質問

リフレッシュトークンの設計・実装で相談の多い5点をまとめます。

リフレッシュトークンの有効期限はどれくらいが妥当ですか?

扱うデータで変わります。社内の参照系なら30日、一般的な業務SaaSで14日、決済や権限変更を含む画面では数時間まで詰めるのが目安です。あわせて、更新のたびに期限を延ばすスライディング方式でも、認可の起点から測る絶対上限を必ず別に設けてください。上限がないと1回のログインが無期限に延命されます。監査要件でセッション最大時間が定められている場合は、その値が上限の天井になります。

リフレッシュトークンはlocalStorageに保存してもよいですか?

SPAでは避けてください。localStorageはJavaScriptから読めるため、XSSが1つ通った時点で攻撃者が持ち出し、自分の環境から任意のタイミングでアクセストークンを再発行できます。メモリ上に保持してタブを閉じたら失う方式が最小限の防御で、それでも寿命を長く取りたいならBFF構成へ移してサーバ側に置きます。ネイティブアプリはOS標準の資格情報保管領域を使い、平文ファイルや設定値には書きません。

ローテーションを有効にすると同時リクエストで失敗します。どう直しますか?

クライアント側とサーバ側の両方で手を打ちます。クライアントは更新処理を1つの非同期処理として共有し、更新中に発生したAPI要求はその完了を待つキューへ入れる。これで同時に複数の更新リクエストが飛ぶ状態が消えます。サーバ側は同一トークンの再提示を10秒から30秒だけ許容する猶予を設定してください。猶予を数分に広げると、盗まれたトークンを検知する窓が実質的に消えるため、数十秒で止めます。

リフレッシュトークンとセッションIDはどちらを使うべきですか?

フロントとAPIが同一オリジンで完結し、外部クライアントへ権限を配らないなら、セッションクッキーで足ります。失効はサーバ側のセッションを消すだけで済み、経路が1本にまとまるためです。リフレッシュトークンを選ぶのは、外部クライアントへ権限を委譲する場合、クッキーを使えないネイティブアプリやCLIの場合、複数APIをまたいで同一の認可を使い回す場合の3つです。両方を併用するときは、ログアウト時に双方を失効させる処理を必ず接続します。

リフレッシュトークンが発行されないのはなぜですか?

設定どおりの挙動である場合がほとんどです。確認は3点の順で行います。第一に要求スコープへoffline_accessを入れているか。第二にクライアント種別が公開クライアントとして登録され、認可サーバ側で発行が既定で無効になっていないか。第三に、認可サーバのポリシーが同意画面の明示的な再表示を条件にしていないかです。この順で追えば、多くは2手目までに原因へ到達します。

関連記事

資料請求

RELATED POSTS 関連記事