【図解】リフレッシュトークンとは?アクセストークンを取り直すしくみと守り方を、Entra IDの例で初心者向けに解説

クライアントクレデンシャルの記事では、人がいないアプリにはリフレッシュトークンがない、と書きました。では、人がサインインして使う認可コードフローでは、アクセストークンの期限が切れたらどうしているのか。今回はその取り直しのしくみであるリフレッシュトークンを、RFC と Microsoft(Entra ID)のドキュメントで確かめながら、最初の準備から一歩ずつ図にしました。

この記事でわかること
  1. リフレッシュトークンは、利用者にもう一度サインインしてもらわずに、アクセストークンを取り直すためのもの
  2. リフレッシュトークンは認可サーバにだけ送り、API には送らない
  3. サインインから、期限切れ、取り直しまでの流れ(Entra ID の場合)
  4. 取り消すと何が止まり、何が残るか
  5. 長く使えるぶん、置き場所・送り先・後始末で守る

まずはたとえで:入館証と引換券

アクセストークンは、ビルの入館証のようなものです。各フロアの扉(API)を通るたびに見せるので、見せる回数が多く、落とす機会も多くなります。そこで期限を短くしておき、落としてもすぐに使えなくなるようにします。

リフレッシュトークンは、入館証を出し直してもらうための引換券です。引換券は受付(認可サーバ)にだけ出し、扉には見せません。期限は長めですが、受付は「本人の券か」「取り消されていないか」を確かめてから、新しい入館証を出します。

アクセストークン = 入館証入館証見せる各フロアの扉(API)扉を通るたびに見せる見せる回数が多いので、期限を短くする(Entra ID では1時間ほど)落としても、すぐに使えなくなるリフレッシュトークン = 引換券引換券受付(Entra ID)新しい入館証受付にだけ出す(扉には見せない)期限は長い(Entra ID では既定で90日)受付は、本人の券か、取り消されていないかを確かめてから出し直すよく見せる入館証は短く。長く使う引換券は、受付にしか出さない
図1 入館証(アクセストークン)と引換券(リフレッシュトークン)

なぜ2種類に分けるのか

アクセストークンが1種類だけだと、期限を長くすれば漏れたときの被害が大きくなり、短くすれば利用者に何度もサインインしてもらうことになります。2種類に分けると、この両方を避けられます。

アプリ(サーバ)AT と RT を持つEntra ID(認可サーバ)Microsoft GraphSharePoint自社の APIリフレッシュトークン取り直すときだけアクセストークンAPI を呼ぶたびにアクセストークン送る先が多く、回数も多い→ 期限を短くして、漏れたときの被害を小さくリフレッシュトークン送る先は Entra ID だけ。回数も少ない→ 期限は長い。そのぶん、しっかり守るリフレッシュトークンは API に送らない
図2 2つのトークンを送る先

RFC 6749(OAuth 2.0 の仕様)は、リフレッシュトークンを「アクセストークンを取り直すための資格情報」としています。リフレッシュトークンは認可サーバとのやり取りだけで使い、リソースサーバ(API)には送りません。送る先が1か所で回数も少ないので、長く使える代わりにしっかり守る、という役割分担です。なお、リフレッシュトークンを出すかどうかは認可サーバが決めることで、必ず出るものではありません。

基本の形(RFC):期限が切れたら、リフレッシュトークンで取り直す

RFC 6749 の図に沿って、リフレッシュトークンが出てくる流れを見ます。(A)〜(D) は、これまでの記事で見てきたトークンのもらい方と使い方です。(E) 以降が、期限が切れたあとの動きです。

クライアント(アプリ)認可サーバ(例:Entra ID)リソースサーバ(API)ここがリフレッシュ(RFC 6749 の6章)(A) 認可グラント例:認可コード(B) アクセストークン + リフレッシュトークン(C) アクセストークン(D) 保護されたリソース…(C)(D)をくり返す…(E) アクセストークン期限が切れている(F) Invalid Token Error(G) リフレッシュトークン機密クライアントはアプリの証明も付ける(H) 新しいアクセストークン+(任意で)新しいリフレッシュトークンリフレッシュトークンは認可サーバにだけ送る。リソースサーバには送らない
図3 リフレッシュトークンの流れ(RFC 6749 の図をもとに作図)

(E)(F) で API から「トークンが無効」と返ってきたら、(G) でリフレッシュトークンを認可サーバに送り、(H) で新しいアクセストークンを受け取ります。実際のアプリでは、トークンと一緒に受け取った有効期限を見て、API に送る前に取り直すことも多いです。後で見る Microsoft のライブラリ(MSAL)も、期限が近いと分かれば先に取り直します。

(G) リクエスト(RFC 6749 の例)

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
リクエスト(クライアント → 認可サーバのトークンエンドポイント)POST /tokenトークンエンドポイントへ。TLS が必須Authorization: Basic …アプリの証明。機密クライアントは必須grant_type=refresh_tokenリフレッシュを頼む合図refresh_token=…持っているリフレッシュトークンscope=…(任意)最初に許された範囲まで。省くと同じ範囲応答(認可サーバ → クライアント)access_token新しいアクセストークンrefresh_token(任意)新しいものが来たら、古いものは捨てるリフレッシュトークンは、発行されたクライアントに結び付いている
図4 リクエストと応答の中身(RFC 6749)

Authorization: Basic は、アプリの証明(クライアント ID とシークレット)です。RFC 6749 は、リフレッシュトークンを発行したクライアントに結び付けておくよう求めていて、機密クライアント(シークレットや鍵を安全に保管できる、サーバで動くアプリ)は、取り直すときにもアプリの証明を付けなければなりません。リフレッシュトークンだけを手に入れても、アプリの証明がなければ使えない、ということです。

scope は省略できます。省くと最初に許された範囲と同じになり、付ける場合も、最初に利用者が許した範囲を超えることはできません。

(H) 応答(RFC 6749 の例)

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token":"2YotnFZFEjr1zCsicMWpAA",
  "token_type":"example",
  "expires_in":3600,
  "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
  "example_parameter":"example_value"
}

RFC 6749 の応答の例です。取り直しの応答には、新しいリフレッシュトークンが入ることがあります。RFC 6749 では、新しいリフレッシュトークンを受け取ったクライアントは古いものを捨て、新しいものに置き換えなければなりません。認可サーバの側は、古いものを取り消してもよい、とされています。

誰が何を持つか

ここからは Microsoft の場合です。Entra ID と、サーバで動く Web アプリ(機密クライアント)を例にします。流れを追う前に、誰が何を持っているかを先に押さえておきます。

利用者(ブラウザ)アプリ(サーバ)Entra IDAPI(例:Graph)パスワードなどEntra ID にだけ入力するトークンは持たないアプリの画面を見るだけアプリの証明シークレット(や証明書)アクセストークンAPI に見せる・約1時間リフレッシュトークンEntra ID にだけ出す・長いどちらもキャッシュに置く(サーバの中)アプリの証明の照合用シークレットを確かめる署名用の秘密鍵AT に署名する利用者ごとの時刻これより前の RT は無効RT は暗号化されていて読めるのは Entra ID だけEntra ID の公開鍵AT の署名を検証するRT は届かない受け取ることはないリフレッシュトークンはここだけを往復アクセストークンは API へ長く使える RT は、アプリのサーバと Entra ID の間から出さない
図5 誰が何を持つか(Entra ID と Web アプリの場合)
  • 利用者(ブラウザ):パスワードなどは Entra ID のサインイン画面にだけ入力します。この記事の例では、トークンはサーバ側のアプリが持ち、ブラウザには渡しません。
  • アプリ(サーバ):アプリの証明(この記事ではシークレット)と、アクセストークン・リフレッシュトークンを持ちます。トークンはサーバの中のキャッシュ(一時的な保存場所)に置きます。
  • Entra ID:アプリの証明を確かめるための情報、トークンに署名する秘密鍵、利用者ごとの「この時刻より前に出したトークンは無効」という時刻を持ちます。Microsoft のリフレッシュトークンは暗号化されていて、中身を読めるのは Entra ID だけです。
  • API:Entra ID の公開鍵を使って、アクセストークンの署名を検証します。リフレッシュトークンは受け取りません。
補足:シークレットの代わりに証明書

この記事では、Microsoft のドキュメントの例に合わせて、アプリの証明をシークレットにしています。本番では証明書が勧められていて、その場合の鍵の動きはクライアントクレデンシャルの記事の図6・図7と同じです。

クライアントクレデンシャルとの違い

図5のアプリは、シークレットを持っています。クライアントクレデンシャルの記事でも、アプリはシークレット(や証明書)を持っていて、そこではリフレッシュトークンは出ませんでした。同じようにシークレットを持つのに、なぜこの記事ではリフレッシュトークンが出るのか。これは、2つの別々の軸で整理すると分かります。

  • 誰の権限で動くか:人の代わりに動くなら認可コードフロー、アプリ自身として動くならクライアントクレデンシャルです。
  • アプリが秘密を持てるか:サーバで動くアプリ(機密クライアント)は、どちらのフローでも、シークレットや証明書で「このアプリです」と証明します。SPA やスマホのアプリ(公開クライアント)は秘密を置けないので、証明しません。

この記事の例は「人の代わり」で、しかも「機密クライアント」です。そのため、リフレッシュトークンとシークレットの両方が出てきます。

人の代わり
機密クライアント
(この記事の例)
人の代わり
公開クライアント
(SPA など)
アプリ自身
(クライアント
クレデンシャル)
フロー認可コードフロー認可コードフロークライアント
クレデンシャル
サインインする人いるいるいない
アプリの証明使う(シークレットや証明書)使わない(置けない)使う(シークレットや証明書)
リフレッシュトークン出る(offline_access を頼んだとき)出る(Entra ID では24時間)出ない
期限が切れたらリフレッシュトークンとアプリの証明で取り直すリフレッシュトークンだけで取り直すアプリの証明だけで、最初と同じ要求をもう一度送る

シークレットが証明するのは「どのアプリか」です。リフレッシュトークンが証明するのは「この利用者が、このアプリにサインインして許可した」ことです。人の代わりに動くアプリは、取り直すたびに利用者に来てもらうわけにはいきません。そこで、利用者のサインインと同意の代わりにリフレッシュトークンを使います。シークレットだけでは、どの利用者の代わりかを示せません。

一方、アプリ自身として動くときは、代わりになる利用者がいません。アプリの証明さえあれば、いつでも最初と同じ要求でトークンを取り直せます。そのため、リフレッシュトークンは要りません。

ポイント

シークレットは「アプリの身分証」で、人の代わりでもアプリ自身でも使います。リフレッシュトークンは「利用者が許可した証拠」で、利用者がいるときだけ出ます。

全体の流れ(Entra ID・Web アプリの場合)

図5のものが、どこで作られ、どこを通って、どこで使われるのかを、一歩ずつ並べます。流れは、最初に1回だけ行う「準備とサインイン」と、期限が切れるたびに行う「実行時」に分かれます。番号は2つの図を通しての番号です。

準備とサインイン(最初に1回):①〜⑬

①〜④は管理者がアプリを登録する作業、⑤〜⑬は利用者が初めてサインインするときの流れです。認可コードフローそのものは認可コードフローの記事でくわしく見たので、ここではリフレッシュトークンに関わるところを中心に見ます。

管理者利用者(ブラウザ)アプリ(サーバ)Entra IDアプリを登録する(管理者・最初に1回)サインインする(利用者・最初に1回)① アプリを登録するリダイレクト URI を書く。クライアント ID が決まる② クライアントシークレットを作るCertificates & secrets ›› New client secretEntra ID が値を作る照合用の情報が Entra ID に残る③ シークレットの値画面に一度だけ表示される④ サーバで操作:設定に書くクライアント ID・シークレット⑤ アプリを開く⑥ サインイン画面へ転送するリダイレクト⑦ GET /authorizescope に offline_access を入れる⑧ サインインし、同意するoffline_access の同意も出る⑨ 認可コードブラウザ経由で届く⑩ POST /token認可コード + シークレット + code_verifier⑪ 確かめて、トークンを作るAT:Entra ID の秘密鍵で署名RT:暗号化(Entra ID だけ読める)⑫ AT + RT(+ ID トークン)⑬ キャッシュに保存するサーバの中だけ。ブラウザには渡さないRT をもらう条件は offline_access。もらった RT はサーバから出さない
図6 準備とサインインの流れ:アプリを登録してから、トークンをキャッシュに置くまで
手順誰が・どこで何をするか鍵(物)はどうなるか・使う鍵
①管理者・管理センターアプリを登録し、リダイレクト URI(認可コードを返す先)を書くクライアント ID が決まる
②管理者・管理センターCertificates & secrets ›› New client secret でシークレットを作るEntra ID が値を作り、照合用の情報を持つ
③管理センター → 管理者シークレットの値が、画面に一度だけ表示される秘密の値が管理者の手元を通る
④管理者・サーバで操作クライアント ID とシークレットを、アプリの設定に書く秘密の値がサーバに置かれる
⑤利用者 → アプリブラウザでアプリを開く―
⑥アプリ → ブラウザEntra ID のサインイン画面へ転送する(リダイレクト)―
⑦ブラウザ → Entra IDscope に offline_access を入れて、認可を頼むリフレッシュトークンを頼む合図
⑧利用者・Entra IDサインインし、同意画面で許可するパスワードなどは Entra ID にだけ入る
⑨Entra ID → アプリ認可コードが、ブラウザ経由でアプリに届く認可コードが動く(トークンではない)
⑩アプリ → Entra ID認可コード・シークレット・code_verifier(PKCE の値)を付けて、トークンを頼むシークレットを送る
⑪Entra IDシークレットと認可コードを確かめ、トークンを作るAT は Entra ID の秘密鍵で署名。RT は暗号化
⑫Entra ID → アプリアクセストークン・リフレッシュトークン・ID トークンを返すAT と RT がアプリに届く
⑬アプリトークンをサーバの中のキャッシュに保存するAT と RT はサーバから出ない

リフレッシュトークンに関わるのは⑦と⑫です。⑦で offline_access を頼まなければ、⑫でリフレッシュトークンは返りません。⑫で届いたリフレッシュトークンは、ブラウザに渡さず、サーバのキャッシュに置きます。

実行時(期限が切れるたび):⑭〜㉔

実行時に利用者は何もしません。アプリ、Entra ID、API の3者でやり取りします。

利用者(ブラウザ)アプリ(サーバ)Entra ID(トークン EP)API(例:Graph)API を呼ぶ(⑭〜⑯)期限が切れたら取り直す(⑰〜㉓)もう一度 API を呼ぶ(㉔)⑭ Bearer トークンで呼ぶAuthorization ヘッダーに AT⑮ 署名を検証Entra ID の公開鍵で⑯ 200 + データ⑰ AT の期限切れに気づくキャッシュの有効期限(約1時間)⑱ POST /tokenrefresh_token + RT + シークレット⑲ アプリを確かめるシークレットが合うか(クライアント認証)⑳ RT を確かめるこのアプリ・利用者のものか、期限、取り消し㉑ 新しい AT と RT を作るAT は Entra ID の秘密鍵で署名㉒ 新しい AT + 新しい RT㉓ キャッシュを置き換える古い RT は消す利用者は何もしない(画面のまま)㉔ 新しい AT で呼ぶこのあとは ⑮⑯ と同じ利用者がサインインし直さなくても、アプリはトークンを取り直せる
図7 実行時の流れ:API を呼び、期限が切れたら取り直すまで
手順誰が・どこで何をするか鍵(物)はどうなるか・使う鍵
⑭アプリ → APIアクセストークンを Authorization: Bearer ヘッダーに付けて、API を呼ぶAT だけを送る。RT は送らない
⑮APIトークンの署名を検証し、中身を見て判断するEntra ID の公開鍵で検証
⑯API → アプリ応答(200 とデータ)を返す―
⑰アプリキャッシュの有効期限を見て、アクセストークンの期限切れに気づく―
⑱アプリ → Entra IDgrant_type=refresh_token で、リフレッシュトークンとシークレットを付けて頼む(RFC の G)RT とシークレットを送る
⑲Entra IDシークレットが合うかを確かめる(クライアント認証)照合用の情報で確かめる
⑳Entra IDリフレッシュトークンを確かめる。このアプリとこの利用者に出したものか、期限が切れていないか、取り消されていないかRT を復号して読む(読めるのは Entra ID だけ)
㉑Entra ID新しいアクセストークンとリフレッシュトークンを作るAT は Entra ID の秘密鍵で署名
㉒Entra ID → アプリ新しいアクセストークンと、新しいリフレッシュトークンを返す(RFC の H)新しい AT と RT が届く
㉓アプリキャッシュを新しいものに置き換え、古いリフレッシュトークンを消す古い RT を残さない
㉔アプリ → API新しいアクセストークンで API を呼ぶ。このあとは⑮⑯と同じ新しい AT だけを送る

リフレッシュトークンが動くのは、⑱(アプリから Entra ID へ)と㉒(Entra ID からアプリへ)だけです。API との間(⑭・㉔)を流れるのは、いつもアクセストークンです。また⑱では、リフレッシュトークンと一緒にアプリの証明も送ります。リフレッシュトークンだけでは取り直せないのは、RFC で見たとおりです。

Microsoft(Entra ID)での手順

図6・図7の番号に沿って、実際のリクエストを見ます。

offline_access を頼む(図6の⑦⑧)

Microsoft identity platform(v2.0 のエンドポイント)では、リフレッシュトークンを受け取るには、scope に offline_access を明示して頼む必要があります。offline_access は OpenID Connect で決められたスコープで、同意画面には「Maintain access to data you have given it access to」という許可として表示されます。

利用者が委任されたアクセス許可を何か1つでも同意していれば、offline_access も同意済みとして扱われます。また MSAL は、offline_access を自分で付けて頼みます。そのため MSAL を使うアプリでは、コードに書かなくてもリフレッシュトークンを受け取ります。

リフレッシュトークンで取り直す(図7の⑱〜㉒)

⑱では、トークンエンドポイントに grant_type=refresh_token を送ります。Microsoft のドキュメントに載っているリクエスト例です。値はドキュメントの例のものです。

⑱ リクエスト(Microsoft のドキュメントの例)

// Line breaks for legibility only

POST /{tenant}/oauth2/v2.0/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

client_id=00001111-aaaa-2222-bbbb-3333cccc4444
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read
&refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
&grant_type=refresh_token
&client_secret=sampleCredentia1s    // NOTE: Only required for web apps. This secret needs to be URL-Encoded
パラメータ意味
grant_typerefresh_token にする
refresh_token⑫で受け取ったリフレッシュトークン
client_secretWeb アプリでは必須(⑲のクライアント認証)。秘密を置けないネイティブアプリでは使わない
scope任意。最初に頼んだ範囲と同じか、その一部にする

Microsoft のリフレッシュトークンは、利用者とアプリの組み合わせに結び付いていて、特定の API には結び付いていません。そのため、利用者がこのアプリに同意した許可の範囲であれば、最初とは別の API 向けのアクセストークンも、同じリフレッシュトークンで取れます。

⑲〜㉑で Entra ID が確かめてトークンを作り、㉒で返します。

㉒ 応答(Microsoft のドキュメントの例)

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik5HVEZ2ZEstZnl0aEV1Q...",
    "token_type": "Bearer",
    "expires_in": 3599,
    "scope": "https%3A%2F%2Fgraph.microsoft.com%2Fmail.read",
    "refresh_token": "AwABAAAAvPM1KaPlrEqdFSBzjqfTGAMxZGUTdM0t4B4...",
    "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhdWQiOiIyZDRkMTFhMi1mODE0LTQ2YTctOD..."
}

応答の refresh_token は、新しいリフレッシュトークンです。Microsoft のドキュメントは、古いものをこの新しいものに置き換えるよう書いています。なお、この refresh_token も、⑦で offline_access を頼んだときだけ返ります。

キャッシュを置き換え、古いものを消す(図7の㉓)

Entra ID のリフレッシュトークンは、使うたびに新しいものに入れ替わります。ここで気を付けたいのは、Entra ID は、使われた古いリフレッシュトークンを自動では取り消さないことです。Microsoft のドキュメントは、新しいものを受け取ったら、古いものを安全に消すよう書いています。

㉓の前(アプリのキャッシュ)AT(期限切れ)RT1+ ㉒で届いた AT2・RT2㉓のあとAT2(新しい)RT2(新しい)RT1:消すEntra ID は、使われた RT1 を自動では取り消さない取り消されていないので、手元に残すと、漏れたときに使われるおそれがある。アプリの側で安全に消すRT は置き換えて、古いほうは残さない
図8 ㉓でキャッシュを置き換える

MSAL に任せる

実際には、ここまでのやり取りを自分で書くことはあまりありません。MSAL では、アクセストークンが要るたびに acquireTokenSilent を呼びます。キャッシュに使えるアクセストークンがあればそれを返し、期限が近いか切れていれば、キャッシュのリフレッシュトークンで自動的に取り直します(図7の⑰〜㉓)。取り直せないとき(リフレッシュトークンの期限切れや取り消し)は、利用者にもう一度サインインしてもらう画面に切り替えます。

acquireTokenSilent を先に呼び、だめなら対話に切り替える(Microsoft のドキュメントの例:MSAL.js)

var request = {
    scopes: ["User.Read"],
};

msalInstance.acquireTokenSilent(request).then(tokenResponse => {
    // Do something with the tokenResponse
}).catch(async (error) => {
    if (error instanceof InteractionRequiredAuthError) {
        // fallback to interaction when silent call fails
        return msalInstance.acquireTokenPopup(request);
    }

    // handle other errors
})

この例はブラウザで動く MSAL.js のものですが、「まず静かに取り、だめなら対話で取る」という順番は、ほかの言語の MSAL でも同じです。また MSAL は、セキュリティのため、リフレッシュトークンをアプリのコードに渡しません。リフレッシュトークンを自分で取り出して保存するのではなく、MSAL のキャッシュに任せるのが基本です。

取り消し:リフレッシュトークンを止めると、どうなるか

退職や端末の紛失のときは、管理者が利用者のリフレッシュトークンを取り消します。取り消したときの動きを、図7と同じように並べます。図7と区別するため、番号は黒丸(❶❷…)にしています。

管理者アプリ(サーバ)Entra IDAPI(例:Graph)❶ サインインセッションを取り消すRevoke-MgUserSignInSession など❷ 取り消しの時刻を記録するこの時刻より前に出した RT は使えない❸ 期限内の AT で呼ぶ❹ 200 + データAT は期限まで使える(CAE 対応の API は別)❺ AT の期限切れに気づく❻ POST /tokenrefresh_token + 取り消し前の RT❼ RT を確かめる取り消しの時刻より前に出したもの❽ invalid_grantAADSTS50173❾ キャッシュの RT を消す利用者をサインイン画面へ(図6の⑥から)取り消しは RT に効く。発行済みの AT は期限が切れるまで残る
図9 取り消したときの流れ
手順誰が・どこで何をするか図7との違い
❶管理者 → Entra ID利用者のサインインセッションを取り消す図7にはない操作
❷Entra ID取り消しの時刻を記録する。この時刻より前に出したリフレッシュトークンは使えなくなる―
❸❹アプリ・API期限内のアクセストークンで API を呼び、応答が返る⑭〜⑯と同じ。アクセストークンはまだ使える
❺❻アプリ → Entra ID期限切れに気づき、リフレッシュトークンで取り直しを頼む⑰⑱と同じ
❼Entra IDリフレッシュトークンが、取り消しの時刻より前に出したものだと分かる⑳で止まる
❽Entra ID → アプリinvalid_grant(AADSTS50173)を返す㉑㉒の代わり。新しいトークンは出ない
❾アプリキャッシュのリフレッシュトークンを消し、利用者をサインイン画面に送る図6の⑥からやり直す

ポイントは❸❹です。取り消しが効くのはリフレッシュトークンで、すでに出ているアクセストークンは、期限が切れるまで使えます。Microsoft のドキュメントも、アクセストークンを取り消せるのは、アプリが継続的アクセス評価(CAE)に対応している場合だけとしています。アクセストークンの期限を短くしている理由が、ここにも表れています。

❶は、Microsoft Graph PowerShell の Revoke-MgUserSignInSession で行えます。利用者のプロパティ signInSessionsValidFromDateTime を今の時刻にして、その利用者のアプリに出したリフレッシュトークンをすべて無効にします。必要な許可は、もっとも弱いもので User.RevokeSessions.All です。

❶ 取り消す(Microsoft のドキュメントの例)

Import-Module Microsoft.Graph.Users.Actions

# A UPN can also be used as -UserId.
Revoke-MgUserSignInSession -UserId $userId

取り消しは、管理者の操作のほかに、パスワードの変更などでも起きます。ただし、どの出来事で取り消されるかは、アプリの種類によって違います。Microsoft のドキュメントの表から、主なものを抜き出しました。

出来事公開クライアント
(パスワードでサインイン)
機密クライアント
(Web アプリなど)
パスワードの期限が切れる残る残る
利用者がパスワードを変える取り消される残る
利用者がセルフサービスでパスワードをリセットする取り消される残る
管理者が Microsoft Entra 管理センターでパスワードをリセットする取り消される取り消される
利用者が自分のリフレッシュトークンを取り消す取り消される取り消される
管理者が利用者のリフレッシュトークンをすべて取り消す取り消される取り消される
シングルサインアウト残る残る

利用者が自分でパスワードを変えても、Web アプリ(機密クライアント)に出したリフレッシュトークンは残ります。アカウントが漏れた疑いがあるときは、パスワードを変えるだけでなく、管理者がリフレッシュトークンを取り消す、と覚えておくとよさそうです。なお、ほかの組織から招待したゲスト(B2B)の利用者は、招待した側のテナントでは取り消されず、利用者が所属するテナントで取り消す必要があります。

期限:Entra ID の既定値

Entra ID のトークンの期限は、次のようになっています。

アクセストークン約60〜90分RT(Web アプリなど)90日・使うたびに延びるRT(SPA)24時間で終わる期限が来るたびに、RT で取り直す(ここでは長さは目安)● 使う使うたびに、そこから90日に延びる使われないまま90日→ 期限切れ(AADSTS700082)最初の RT から24時間新しい RT も同じ時刻で終わる→ AADSTS700084終わったら、もう一度サインインの流れ(多くは画面の再読み込みだけ)AT は短く取り直し、RT は使われ方で期限が決まる
図10 トークンの期限(Entra ID の既定値。長さは目安)
トークン期限補足
アクセストークン60〜90分の間(平均75分)期限はこの範囲で決まる。CAE に対応したアプリには、もっと長いトークンが出ることがある
リフレッシュトークン(Web アプリ・ネイティブアプリなど)90日使うたびに、そこから数え直す。使われないまま期限を過ぎると AADSTS700082
リフレッシュトークン(SPA)24時間取り直して得た新しいリフレッシュトークンも、最初の期限を引き継ぐ。切れると AADSTS700084
リフレッシュトークン(メールのワンタイムパスコードでサインイン)24時間―

SPA(ブラウザだけで動くアプリ)のリフレッシュトークンは、24時間で必ず終わります。そのため SPA は、24時間ごとに認可コードフローをやり直す前提で作ります。Microsoft のドキュメントによると、利用者がパスワードを入れ直す必要はなく、多くの場合はアプリの画面が再読み込みされるだけです。ただし、サードパーティ Cookie を止めているブラウザがあるため、サインイン画面はページ全体の移動かポップアップで開く必要があります。

補足:期限が残っていても使えなくなることがある

リフレッシュトークンは、期限より前でも、取り消しや条件付きアクセスのサインイン頻度の設定などで使えなくなります。アプリは「期限までは必ず使える」と考えず、取り直せなかったら対話のサインインに切り替える作りにしておきます。

守り方:長く使えるぶん、しっかり守る

リフレッシュトークンは長く使えるので、漏れたときの影響も長く続きます。まず、RFC が決めている守り方を見ます。

ローテーション:使うたびに入れ替えて、漏れに気づく

RFC 6749 と RFC 9700(OAuth 2.0 のセキュリティのベストプラクティス)は、リフレッシュトークンのローテーション(使うたびに新しいものに入れ替えること)を、漏れに気づくためのしくみとして説明しています。

アプリ(正しい持ち主)認可サーバ第三者(RT1 の写しを持つ)(1) RT1 で取り直す(2) AT + RT2RT1 はここで使えなくする(3) 漏れた RT1 で頼む(4) 使えなくした RT1 が届いた→ 漏れたと分かるどちらが本物か区別できないので、使える RT2 も取り消す(5) 拒否invalid_grant(6) RT2 で頼む(7) 拒否利用者にサインインし直してもらう毎回 RT を入れ替えると、古い RT が使われたときに漏れに気づける
図11 ローテーションで漏れに気づくしくみ(RFC 6749・RFC 9700)

(2) で、使った RT1 は無効になります。そのあと (3) で RT1 が届いたら、どこかで写しが使われた、つまり漏れたと分かります。ただ、認可サーバには、どちらが正しい持ち主か区別がつきません。そこで RFC 9700 では、そのとき使える RT2 も取り消すとしています。正しいアプリも (7) で取り直せなくなり、利用者にサインインし直してもらうことになりますが、漏れたリフレッシュトークンを使い続けられることは防げます。

そのほかの守り方

置き場所サーバの中か、暗号化した保存先ログや URL に出さないブラウザの画面側には渡さないMSAL は RT を外に出さない送り先認可サーバ(Entra ID)にだけ送るAPI には送らない通信は必ず TLSアプリに結び付けるRT は発行したアプリだけが使える機密クライアントは、取り直すときもアプリの証明を付ける秘密を置けないアプリSPA・スマホのアプリなど(公開クライアント)では、送信者制約かローテーションを使う(RFC 9700)期限と取り消ししばらく使われないと期限切れ管理者が利用者ごとに取り消せる取り消しは、発行済みの AT には効かない(期限まで残る)古い RT の後始末新しい RT が来たら古いものを消すEntra ID は、使われた古い RT を自動では取り消さないRT は「長く使えるパスワード」のつもりで守る
図12 リフレッシュトークンの守り方
  • 置き場所と送り先:RFC 6749 は、リフレッシュトークンを、通信中も保存中も秘密にし、認可サーバと発行先のクライアントの間だけで共有するよう求めています。送るときは TLS を使います。
  • アプリに結び付ける:認可サーバは、リフレッシュトークンと発行先のクライアントの結び付きを保ち、クライアントを認証できるときは認証しなければなりません。
  • 秘密を置けないアプリ:SPA やスマホのアプリのような公開クライアントは、アプリの証明を持てません。RFC 9700 は、公開クライアントのリフレッシュトークンには、送信者制約(DPoP や mTLS で、トークンを持ち主の鍵に結び付ける方法)か、ローテーションのどちらかを使わなければならないとしています。
  • 期限と取り消し:RFC 9700 は、リフレッシュトークンを一定期間使われなかったら期限切れにすること、パスワードの変更やログアウトで取り消すことにも触れています。

ここからは Microsoft の場合です。Entra ID のリフレッシュトークンは使うたびに入れ替わりますが、STEP 3 で見たとおり、古いものは自動では取り消されません。アプリの側で古いものを消すことが大事になります。また Microsoft は、トークンを守るときは、まずサインインセッションのトークン(リフレッシュトークン)を優先するよう勧めています。数週間から数か月使えるため、盗まれると長く不正に使われるおそれがあるからです。

補足:トークン保護(条件付きアクセス)

Entra ID の条件付きアクセスには、端末に暗号的に結び付いたリフレッシュトークンだけを受け付ける「トークン保護」があります。ほかの端末に持ち出したリフレッシュトークンは受け付けません。対応しているアプリや端末は限られていて、増えていく予定とされているので、使うときは最新のドキュメントで確かめてください。

うまくいかないとき

取り直しで出会いそうなエラーです。多くは、利用者にもう一度サインインしてもらえば直ります。

エラー意味どうするか
AADSTS700082リフレッシュトークンが、長く使われずに期限切れになった利用者に対話のサインインをしてもらう。トークンの寿命どおりの動き
AADSTS700084SPA に出したリフレッシュトークンの、決まった期限が切れたSPA は、サインイン画面を開き直して認可コードフローをやり直す
AADSTS50173リフレッシュトークンが取り消された(パスワードの変更やリセット、管理者の取り消しなど)キャッシュを消し、利用者にサインインし直してもらう
AADSTS70043条件付きアクセスのサインイン頻度の設定で、リフレッシュトークンが使えない利用者にサインインし直してもらう
invalid_clientアプリの証明(クライアント認証)に失敗したシークレットの期限切れや値の誤りがないか。管理者が資格情報を更新する
interaction_required追加の認証(多要素認証など)が必要同じスコープで /authorize からやり直す。MSAL では InteractionRequiredAuthError などになる

RFC 6749 では、リフレッシュトークンが無効・期限切れ・取り消し済み・ほかのクライアントのもの、のどれでも、エラーは invalid_grant になります。Entra ID では、AADSTS で始まる番号で理由が分かります。

企業での例

場面やること押さえどころ
社員のノート PC の紛失や盗難Revoke-MgUserSignInSession で、その社員のサインインセッションを取り消すリフレッシュトークンは止まる。発行済みのアクセストークンは期限まで残る
アカウントが漏れた疑いがあるパスワードのリセットと、リフレッシュトークンの取り消しを両方行う利用者自身のパスワード変更では、Web アプリのリフレッシュトークンは残る
社内向けの SPA を作る24時間ごとのサインインのやり直しを前提に作るサインイン画面は、ページ全体の移動かポップアップで開く
社内の Web アプリで、Graph を長く使うMSAL のキャッシュを、サーバ側の保存先に置くリフレッシュトークンをログやデータベースの平文に書かない
人がいない夜間の処理利用者のリフレッシュトークンを使い続けるより、クライアントクレデンシャルを検討する利用者の退職やパスワードの変更に左右されない

まとめ

項目内容
どんなもの利用者にもう一度サインインしてもらわずに、アクセストークンを取り直すための資格情報
送る先認可サーバだけ。API には送らない
流れ期限切れ → grant_type=refresh_token で取り直す → 新しいアクセストークン(と新しいリフレッシュトークン)。機密クライアントはアプリの証明も付ける
もらう条件Entra ID では scope に offline_access(MSAL は自動で付ける)。クライアントクレデンシャルでは出ない
期限Entra ID の既定は90日で、使うたびに数え直す。SPA は24時間で終わる
置き換え新しいものが来たら古いものを消す。Entra ID は古いものを自動では取り消さない
取り消し管理者が利用者ごとに取り消せる。発行済みのアクセストークンは期限まで残る
守り方サーバの中に置き、ログに出さない。公開クライアントでは送信者制約かローテーション。実装は MSAL に任せる

参考にした資料

この記事は、次の標準と公式ドキュメントで内容を確かめて書きました。仕様や対応状況は変わることがあるので、最新の情報はリンク先で確かめてください。

  • RFC 6749: The OAuth 2.0 Authorization Framework(IETF):リフレッシュトークンの役割と流れ(1.5)、取り直しのリクエストと応答の例(5.1・6)、クライアントへの結び付きと機密クライアントの認証、scope の扱い、新しいリフレッシュトークンを受け取ったら古いものを捨てること(6)、invalid_grant と invalid_client(5.2)、リフレッシュトークンの守り方とローテーションで漏れに気づくこと(10.4)
  • RFC 9700: Best Current Practice for OAuth 2.0 Security(IETF):公開クライアントのリフレッシュトークンに送信者制約かローテーションを使うこと(2.2.2)、ローテーションで使える方を取り消すこと、使われないときの期限切れ、パスワード変更やログアウトでの取り消し(4.14)
  • Refresh tokens in the Microsoft identity platform(Microsoft Learn):利用者とアプリの組み合わせへの結び付き、暗号化されていること、既定の期限(24時間・90日)、使うたびに入れ替わることと古いものが取り消されないこと、SPA の24時間、取り消しの表、B2B の利用者
  • Microsoft identity platform and OAuth 2.0 authorization code flow(Microsoft Learn):取り直しのリクエストと応答の例、パラメータ(client_secret・scope)、同意済みの許可の範囲で別の API のトークンも取れること、refresh_token が offline_access のときだけ返ること、トークンエンドポイントのエラー(interaction_required など)
  • Scopes and permissions in the Microsoft identity platform(Microsoft Learn):offline_access の意味と同意画面の表示、委任されたアクセス許可があれば同意済みとして扱われること、v2.0 では明示して頼むこと
  • Manage tokens for Zero Trust(Microsoft Learn):MSAL が offline_access を常に頼むこと
  • Access tokens in the Microsoft identity platform(Microsoft Learn):アクセストークンの期限(60〜90分、平均75分)と CAE での長い期限
  • Understanding tokens in Microsoft Entra ID・Protecting tokens in Microsoft Entra(Microsoft Learn):リフレッシュトークンの期限が使うたびに数え直されること、サインインセッションのトークンを優先して守ること、アクセストークンは CAE 対応のときだけ取り消せること、条件付きアクセスのトークン保護
  • Revoke-MgUserSignInSession(Microsoft Learn):signInSessionsValidFromDateTime を今の時刻にしてリフレッシュトークンを無効にすること、紛失・盗難時の操作であること、必要な許可、コマンドの例
  • Acquiring and using an access token・Token caching in MSAL Node(Microsoft Learn):acquireTokenSilent がキャッシュのリフレッシュトークンで自動的に取り直すこと、失敗したら対話に切り替える例、MSAL がリフレッシュトークンを外に出さないこと
  • Microsoft Entra authentication and authorization error codes・Error AADSTS50173 – The provided grant has expired due to it being revoked(Microsoft Learn):AADSTS700082・AADSTS700084・AADSTS70043・AADSTS50173 の意味と対処

あとがき

リフレッシュトークンは「長く使えるトークン」と覚えていましたが、流れを一歩ずつ並べてみると、大事なのは「API には送らない」「取り直すときもアプリの証明を付ける」という、送り先と結び付きのほうでした。もうひとつ意外だったのは、利用者が自分でパスワードを変えても、Web アプリのリフレッシュトークンは残ることです。端末の紛失や退職のときの手順に、取り消しを入れておきたいと思いました。

コメント