+ 作るログイン
図を読み込んでいます…
1 / 25

IDトークン・アクセストークン・リフレッシュトークン ― 誰に見せて、誰が確かめるのか

発行
アニメモ公式
版
第1版
改訂
コマ
全25

ログインすると届く3つのトークンを、見せる相手・確かめる人・期限・漏れたときの危なさ・置き場所の順に、1枚の図を動かしながら見ていきます。

3つのトークンは、見せる相手が違う

ログインの仕組みを調べると、IDトークン・アクセストークン・リフレッシュトークンの3つが必ず出てきます。どれも「長い文字列」で、どれも「JWT」と呼ばれがちなので、違いがぼやけやすいところです。

いちばん大事な違いは、誰に見せるためのものかです。IDトークンはアプリに「誰がログインしたか」を伝えます。アクセストークンは API に「この API を使ってよいか」を示します。リフレッシュトークンは、トークンを出した窓口(認可サーバー)にだけ見せて、新しいトークンを取り直すために使います。

見せる相手が違えば、確かめる人も、期限も、漏れたときの危なさも、置き場所も変わります。この作品では、1枚の図を動かしながら、その5つを順に見ていきます。

  • 認証のしくみ ― 3つのトークン
  • 1 / 25
  • 3つのトークンは、見せる相手が違う
  • 書類
  • IDトークン
  • ブラウザ
  • アプリ
  • 誰がログインしたか
  • photo.example
  • 鍵
  • アクセストークン
  • サーバー
  • API
  • API を使ってよいか
  • api.example(写真を返す)
  • 更新
  • リフレッシュトークン
  • 守られている
  • 認可サーバー
  • 新しいトークンを取り直す
  • login.example(ログインの窓口)
  • ひらめき
  • IDトークンはアプリへ、アクセストークンは API へ、リフレッシュトークンは窓口へ

登場するのは、あなた・アプリ・窓口・API の4者

例として、写真を見せるアプリ photo.example を考えます。あなたの写真は、別のサーバー api.example(API)に置いてあり、アプリはそこから写真を取ってきて表示します。

ログインを受け付けるのは、アプリでも API でもなく、login.example という窓口です。正式には認可サーバー(OpenID Connect では OpenID プロバイダー)と呼びます。Google や Microsoft、Auth0、Supabase Auth などが、この役をよく引き受けています。

図の右上が窓口、右下が API、左があなたのブラウザとアプリです。この先、トークンを出すのはいつも窓口で、受け取って使う側が3者いる、という形はずっと変わりません。

  • 認証のしくみ ― 3つのトークン
  • 2 / 25
  • 登場するのは、あなた・アプリ・窓口・API の4者
  • アプリ(photo.example)は、あなたの写真を
  • API(api.example)から取ってきて見せたい
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • サーバー
  • API
  • api.example(写真を返す)
  • ひらめき
  • トークンを出すのはいつも窓口(認可サーバー)。受け取って使う側が3者いる

JWT は「形式」の名前で、「役割」の名前ではない

まず言葉を1つ整理します。JWT(JSON Web Token、RFC 7519)は、トークンの書き方の形式です。「IDトークン」「アクセストークン」が役割の名前であるのとは、話の段が違います。

JWT は、ドット(.)で区切った3つの部分でできています。ヘッダーには署名の方式、ペイロードには中身(「クレーム」と呼ぶ項目の集まり)、最後の署名には「上の2つを書き換えていない」ことの証拠が入ります。

3つのトークンのうち、IDトークンは必ず JWT です(OpenID Connect がそう決めています)。アクセストークンは JWT のこともあれば、意味の無い乱数のこともあります。リフレッシュトークンは、たいてい乱数です。だから「JWT のリフレッシュトークン」という言い方は、多くの場合あまり正確ではありません。

  • 認証のしくみ ― 3つのトークン
  • 3 / 25
  • JWT は「形式」の名前で、「役割」の名前ではない
  • eyJhbGciOiJSUzI1Ni…
  • .
  • eyJpc3MiOiJodHRwczovL2xvZ2lu…
  • .
  • SflKxwRJSMeKKF2QT4fwpM…
  • ヘッダー
  • ペイロード(中身)
  • 署名
  • 署名の方式(RS256 など)
  • 誰が・誰宛て・いつまで(クレーム)
  • 上の2つを書き換えていない証拠
  • 書類
  • IDトークン
  • 鍵
  • アクセストークン
  • 更新
  • リフレッシュトークン
  • 必ず JWT
  • JWT のことも、ただの乱数のことも
  • たいてい、ただの乱数
  • ひらめき
  • ドットで区切った3つ:ヘッダー・ペイロード(中身)・署名

ペイロードは誰でも読める。暗号ではない

JWT の各部分は base64url という方式で文字に置き換えてあるだけです。元に戻すのに鍵は要りません。jwt.io のようなサイトに貼れば、誰でも中身を読めます。

中身はこのような JSON です。iss は発行した窓口、sub はログインした人の ID、aud は受け取る相手(audience)、exp は期限です。

署名が守ってくれるのは「書き換えていない」ことだけで、「読めない」ことではありません。だから、パスワードや API の秘密鍵のような値はペイロードに入れません。読まれては困る情報を運びたいときは、暗号化した形式(JWE)という別の仕組みを使います。

exp は「1970年1月1日からの秒数」で書きます。図の 1792026900 は、日本時間の2026年10月15日 10時15分です。

  • 認証のしくみ ― 3つのトークン
  • 4 / 25
  • ペイロードは誰でも読める。暗号ではない
  • eyJpc3MiOiJodHRwczovL2…
  • {
  • base64url を戻すだけ
  • "iss": "https://login.example",
  • "sub": "user_8f2a",
  • "aud": "photo.example",
  • OK
  • 誰が・誰宛て・いつまで、を書く
  • "exp": 1792026900,
  • "email": "[email protected]"
  • NG
  • パスワードや秘密の値は書かない
  • }
  • NG
  • 暗号化はされていない(だれでも読める)
  • exp は「1970年からの秒数」。
  • 1792026900 = 2026年10月15日 10時15分(日本時間)
  • ひらめき
  • 署名は「書き換えていない」を保証するだけ。秘密は入れない

窓口が秘密鍵で署名し、受け取る側は公開鍵で確かめる

JWT の署名は、多くの場合 RS256 や ES256 という方式で作ります。窓口は秘密鍵で署名し、それと対になる公開鍵を誰でも取れる場所に置きます。この公開鍵の一覧を JWKS(JSON Web Key Set)と呼びます。

受け取る側(アプリや API)は、JWKS から公開鍵を取ってきて、署名が合うかを手元で確かめます。合えば「窓口が出したもので、途中で書き換えられていない」と分かります。1文字でも書き換えると署名は合いません。

ポイントは、確かめるたびに窓口へ問い合わせなくてよいことです。公開鍵は手元に置いておけるので、API はリクエストのたびに窓口と通信せずに済みます。速くて、窓口が止まっていても動けます。ただしこの「問い合わせない」性質が、あとで「取り消しにくい」という弱点にもなります。

なお、窓口と受け取る側が同じ秘密の値を共有する HS256 という方式もあります。この場合は確かめる側も署名を作れてしまうので、窓口と受け取る側が同じ組織の中にいるときに限って使います。

  • 認証のしくみ ― 3つのトークン
  • 5 / 25
  • 窓口が秘密鍵で署名し、受け取る側は公開鍵で確かめる
  • 錠
  • 秘密鍵(窓口だけが持つ)
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • 公開鍵(JWKS)を配る
  • 人
  • ブラウザ
  • 公開鍵(JWKS)
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • OK
  • 手元で確かめる
  • サーバー
  • API
  • 中身を1文字でも書き換えると、署名が合わなくなる
  • OK
  • 手元で確かめる
  • api.example(写真を返す)
  • ひらめき
  • 公開鍵があれば、窓口に問い合わせずに手元で確かめられる

ログインすると、窓口から3つのトークンが届く

あなたがアプリの「ログイン」を押すと、ブラウザは窓口のページに移ります。ID とパスワードを入れるのは窓口のページで、アプリはあなたのパスワードを一度も見ません。これがこの仕組みのそもそもの目的です。

ログインがうまくいくと、窓口からアプリにトークンが届きます。OpenID Connect でログインし、オフラインアクセス(後で取り直すこと)も許可していれば、IDトークン・アクセストークン・リフレッシュトークンの3つです。

実際の手順では、間に「認可コード」の受け渡しが1回はさまります。窓口はまずブラウザ経由で短い使い捨てのコードをアプリに渡し、アプリが裏の通信でそのコードをトークンに引き換えます。トークンそのものをブラウザのアドレス欄に通さないための工夫です。この作品ではそこを省いて、「3つ届く」から話を進めます。

  • 認証のしくみ ― 3つのトークン
  • 6 / 25
  • ログインすると、窓口から3つのトークンが届く
  • 守られている
  • 認可サーバー
  • ID とパスワード
  • login.example(ログインの窓口)
  • トークン3つ
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • 書類
  • IDトークン
  • ブラウザ
  • photo.example
  • 鍵
  • アクセストークン
  • 実際は、間に「認可コード」の
  • 更新
  • リフレッシュ
  • サーバー
  • API
  • 受け渡しが1回はさまる
  • api.example(写真を返す)
  • ひらめき
  • パスワードを見るのは窓口だけ。アプリはパスワードを知らない

IDトークンは「誰がログインしたか」をアプリに伝える

IDトークンは OpenID Connect が決めたトークンで、窓口がアプリに宛てて書いた「この人がログインしました」という証明書です。

中で大事なのは3つです。sub はこの人の ID で、窓口の中で変わらない番号です(メールアドレスは変わることがあるので、人を見分けるには sub を使います)。aud は受け取るアプリ自身の ID(client_id)です。nonce は、アプリがログインを始めるときに作って窓口に渡した合言葉で、IDトークンにそのまま書き戻されてきます。

名前やメールアドレスのような個人の情報が入ることもあります。中身は誰でも読めるので、そのぶん、漏れたときには個人情報が漏れたことになります。

  • 認証のしくみ ― 3つのトークン
  • 7 / 25
  • IDトークンは「誰がログインしたか」をアプリに伝える
  • 書類
  • IDトークン
  • ブラウザ
  • アプリ
  • photo.example
  • {
  • "iss": "https://login.example",
  • "sub": "user_8f2a",
  • OK
  • sub:この人の ID(変わらない)
  • "aud": "photo.example",
  • "exp": 1792029600,
  • OK
  • aud:受け取るアプリ(photo.example)
  • "nonce": "n-0S6_WzA2Mj",
  • "name": "山田 花子"
  • OK
  • nonce:ログインを始めたときの合言葉
  • }
  • OpenID Connect が決めたトークン
  • ひらめき
  • 宛先(aud)はアプリ自身。API に見せるものではない

アプリは IDトークンの5か所を確かめる

IDトークンを確かめるのはアプリです。少なくとも次の5つを見ます。

署名が、窓口の JWKS にある公開鍵で合うこと。iss が、ログインを頼んだ窓口(login.example)であること。aud に自分の client_id が入っていること。exp を過ぎていないこと。nonce が、自分がログインを始めたときに作った値と同じであること。

aud を見ないと、別のアプリ宛てに出された IDトークンを持ち込まれたときに通してしまいます。nonce を見ないと、以前に盗まれた IDトークンを使い回されても気づけません。どれも省いてよい項目ではありません。自分で書かず、窓口が勧めるライブラリに任せるのが安全です。

  • 認証のしくみ ― 3つのトークン
  • 8 / 25
  • アプリは IDトークンの5か所を確かめる
  • 書類
  • IDトークン
  • ブラウザ
  • アプリ
  • photo.example
  • {
  • "iss": "https://login.example",
  • "sub": "user_8f2a",
  • OK
  • 署名が窓口の公開鍵で合う
  • "aud": "photo.example",
  • "exp": 1792029600,
  • OK
  • iss が login.example
  • "nonce": "n-0S6_WzA2Mj",
  • "name": "山田 花子"
  • OK
  • aud が自分(photo.example)
  • }
  • OK
  • exp を過ぎていない
  • OK
  • nonce が自分の出した値
  • ひらめき
  • 1つでも合わなければ、ログインさせない

確かめ終えたら、IDトークンの役目は終わる

5か所を確かめたら、アプリは「この人は user_8f2a だ」と分かります。ここでIDトークンの役目はほぼ終わりです。

この先ログインの状態を覚えておくのは、アプリ自身のセッションです。アプリは自分のサーバーに「このセッションは user_8f2a」と記録し、ブラウザにはその番号を入れた Cookie を渡します。次からは、この Cookie でログイン中だと分かります。

よくある誤解は「IDトークンの exp が切れたらログアウトになる」というものです。IDトークンの期限は「ログインした、という証明書」としての期限で、ログインの状態がいつまで続くかはアプリのセッションが決めます。

  • 認証のしくみ ― 3つのトークン
  • 9 / 25
  • 確かめ終えたら、IDトークンの役目は終わる
  • IDトークンの exp が過ぎても、ログアウトにはならない。
  • 守られている
  • 認可サーバー
  • ログインの状態の期限は、アプリのセッションが決める
  • login.example(ログインの窓口)
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • 書類
  • IDトークン
  • ブラウザ
  • photo.example
  • 確かめ済み。もう使わない
  • サーバー
  • API
  • 錠
  • セッションの Cookie
  • api.example(写真を返す)
  • ひらめき
  • ログインの状態は、アプリが自分のセッションで持つ

アクセストークンは、API に見せる入館証

写真を取りに行くとき、アプリは API へのリクエストにアクセストークンを付けます。Authorization: Bearer に続けて書くのが決まった形です(RFC 6750)。

アクセストークンは、建物の入館証に近いものです。受付(窓口)で一度もらえば、あとは部屋(API)に入るたびに見せるだけです。部屋の側は、入館証が本物で期限内かを見れば、受付に電話して確かめる必要はありません。

アクセストークンが JWT でない(ただの乱数の)場合は、API は手元では確かめられないので、窓口のイントロスペクション(RFC 7662)という口に「このトークンはまだ有効か」を問い合わせます。こちらは毎回問い合わせが要るかわりに、窓口で無効にした瞬間から通らなくなります。

  • 認証のしくみ ― 3つのトークン
  • 10 / 25
  • アクセストークンは、API に見せる入館証
  • GET /photos HTTP/1.1
  • Host: api.example
  • 守られている
  • 認可サーバー
  • Authorization: Bearer eyJhbGciOiJSUzI1NiIs…
  • login.example(ログインの窓口)
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • Bearer アクセストークン
  • サーバー
  • API
  • 鍵
  • アクセストークン
  • api.example(写真を返す)
  • ひらめき
  • Authorization ヘッダーに載せて、リクエストのたびに見せる

API は、アクセストークンを手元で確かめる

アクセストークンを確かめるのは API です。JWT の場合、確かめ方は IDトークンとよく似ています。署名、iss、exp を見て、`aud` が自分(https://api.example) であることを確かめます。

違いは、最後に `scope`(範囲)を見ることです。このトークンは photos:read(写真を読む)だけを許しています。写真を消すリクエストに使われたら、API は断ります。アクセストークンが答えるのは「誰か」よりも「この API を、この範囲で使ってよいか」です。

署名の方式(ヘッダーの alg)は、API の側で決め打ちしておきます。トークンに書いてある方式をそのまま信じると、none(署名なし)や、公開鍵を共有の秘密として使わせる手口で、偽のトークンを通してしまうことがあります。

  • 認証のしくみ ― 3つのトークン
  • 11 / 25
  • API は、アクセストークンを手元で確かめる
  • 鍵
  • アクセストークン
  • サーバー
  • API
  • api.example(写真を返す)
  • {
  • "iss": "https://login.example",
  • "sub": "user_8f2a",
  • OK
  • 署名が窓口の公開鍵で合う
  • "aud": "https://api.example",
  • "scope": "photos:read",
  • OK
  • iss が login.example
  • "client_id": "photo.example",
  • "exp": 1792026900
  • OK
  • aud が自分(api.example)
  • }
  • OK
  • exp を過ぎていない
  • OK
  • scope に photos:read がある
  • ひらめき
  • 見るのは「誰が」より「この API を、この範囲で使ってよいか」

アクセストークンは、持っている人なら誰でも使える

「Bearer」は「持参人」という意味です。アクセストークンは持参人払いの小切手のようなもので、持っている人が誰であっても使えます。API には、正しい持ち主が送ってきたのか、盗んだ人が送ってきたのかの見分けがつきません。

だから、アクセストークンが漏れると、期限が来るまで、あなたになりすまして API を使われます。ログやエラー画面にトークンを書き出してしまう、URL のクエリに載せて履歴やリファラに残る、ブラウザに紛れ込んだスクリプトに読まれる、といった経路があります。

これを防ぐ仕組みとして、トークンを特定の鍵の持ち主に縛る方式もあります(DPoP、RFC 9449)。盗まれても、対になる秘密鍵が無ければ使えません。ただ、まずは「漏らさない」「漏れても短い」が基本です。

  • 認証のしくみ ― 3つのトークン
  • 12 / 25
  • アクセストークンは、持っている人なら誰でも使える
  • 人
  • 攻撃者
  • 守られている
  • 認可サーバー
  • 盗んだトークンを持つ
  • 鍵
  • 盗んだ写し
  • login.example(ログインの窓口)
  • 人
  • ブラウザ
  • Bearer 盗んだ写し
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • 鍵
  • アクセストークン
  • サーバー
  • API
  • api.example(写真を返す)
  • ひらめき
  • API には、正しい持ち主か盗んだ人かの見分けがつかない

JWT は途中で取り消しにくい。だから期限を短くする

JWT のアクセストークンは、API が手元だけで確かめられるのが長所でした。裏を返すと、窓口で「このトークンは無効にした」と決めても、API はそれを知りません。期限(exp)が来るまでは通ってしまいます。

図は、10時に発行したトークンが10時5分に漏れた場合です。期限が15分なら、悪用できるのは10分で終わります。期限が24時間なら、翌日の10時まで悪用され続けます。

取り消しの一覧(拒否リスト)を API に配って、期限前でも止める作り方もあります。ただ、それでは「問い合わせなくてよい」長所が薄れます。多くの場合は、アクセストークンの期限を短くし、期限が来たら取り直すほうを選びます。その取り直しに使うのが、リフレッシュトークンです。

  • 認証のしくみ ― 3つのトークン
  • 13 / 25
  • JWT は途中で取り消しにくい。だから期限を短くする
  • 10:05 に漏れた
  • 期限 15分
  • 悪用できるのは10分
  • 期限 24時間
  • 悪用できるのは、翌日の10時まで(約24時間)
  • 10:00
  • 10:15
  • 10:30
  • 10:45
  • 11:00
  • 10:00 に発行
  • ひらめき
  • API は窓口に問い合わせないので、窓口で無効にしても期限までは通る

期限は、漏れたときの被害と、取り直す手間の釣り合いで決める

期限は仕様では決まっておらず、窓口の設定で決まります。短いほど漏れたときの被害は小さく、そのぶん取り直しの回数が増えます。

よくある目安は、IDトークンが数分〜1時間、アクセストークンが5分〜1時間です。たとえば Google はアクセストークンも IDトークンも1時間、Supabase Auth はアクセストークンが既定で1時間です。

リフレッシュトークンは数日から、期限なしまで幅があります。Google のリフレッシュトークンには決まった期限がなく、6か月使われないと失効します。Supabase Auth のリフレッシュトークンも時間では切れず、1回使うと使えなくなります。長く生きるかわりに、使い方で切れる仕組みを持たせるのがふつうです。この「使い方で切れる」がどう働くかは、少し後で見ます。

  • 認証のしくみ ― 3つのトークン
  • 14 / 25
  • 期限は、漏れたときの被害と、取り直す手間の釣り合いで決める
  • 書類
  • IDトークン
  • 数分〜1時間(例:Google は1時間)
  • 鍵
  • アクセストークン
  • 5分〜1時間(例:Google・Supabase は1時間)
  • 更新
  • リフレッシュトークン
  • 数日〜期限なし。使うたびに入れ替え、使われないまま放っておくと失効
  • IDトークンの期限は「ログインの証明」としての期限。ログインの状態がいつまで続くかではない
  • ひらめき
  • 短い2つは「時間」で切れ、リフレッシュトークンは「使い方」で切れる

期限が切れたら、リフレッシュトークンで取り直す

アクセストークンの期限が切れると、API は 401(認証が必要)を返します。するとアプリは、リフレッシュトークンを窓口に出して、新しいアクセストークンをもらいます。

このとき、あなたにパスワードをもう一度聞く必要はありません。画面の裏で取り直しが済むので、あなたから見ると「ずっとログインしたまま」に見えます。短い期限と使い勝手を両立させるのが、リフレッシュトークンの役目です。

401 を待たずに、exp が近づいたら先に取り直しておく作りもよく使われます。どちらにしても、リフレッシュトークンを受け取るのは窓口だけです。API に見せることはありません。

  • 認証のしくみ ― 3つのトークン
  • 15 / 25
  • 期限が切れたら、リフレッシュトークンで取り直す
  • 更新
  • リフレッシュトークン
  • 鍵
  • 新しいアクセストークン
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • リフレッシュトークン
  • 人
  • ブラウザ
  • 新しいアクセストークン
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • 401 期限切れ
  • サーバー
  • API
  • 鍵
  • アクセストークン
  • api.example(写真を返す)
  • ひらめき
  • あなたにもう一度パスワードを聞かずに、新しいアクセストークンが手に入る

リフレッシュトークンは、窓口にだけ見せる

リフレッシュトークンは、窓口のトークン発行の口(/token)に送ります。中身はたいてい意味の無い乱数で、窓口が自分の台帳(データベース)で「誰の、どのアプリの、いまも有効なトークンか」を引くための番号札です。

確かめるのは窓口です。台帳にあるか、取り消されていないか、そして送ってきたアプリが本当にそのアプリかを見ます。サーバーで動くアプリなら、窓口に登録した秘密の値(クライアントシークレット)なども一緒に出すので、リフレッシュトークンだけを盗んでも使えません。

台帳にある以上、窓口はいつでも1枚ずつ取り消せます。アクセストークンが「取り消しにくい」のと正反対です。長く生かすものだから、取り消せる形で持たせているわけです。

  • 認証のしくみ ― 3つのトークン
  • 16 / 25
  • リフレッシュトークンは、窓口にだけ見せる
  • 更新
  • リフレッシュトークン
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • POST /token
  • grant_type=refresh_token
  • refresh_token=8xLq2v9Tz0aK…(ただの乱数)
  • client_id=photo.example
  • トークン
  • 持ち主
  • 状態
  • 8xLq2v9T…
  • user_8f2a
  • 有効
  • Qm4Rt7s1…
  • user_31cd
  • 取り消し済み
  • 中身に意味は無い。台帳を引く番号札
  • ひらめき
  • 窓口の台帳にあるので、窓口はいつでも取り消せる

リフレッシュトークンが漏れると、何度でも取り直される

一方で、ブラウザやスマホのアプリは、秘密の値を安全に持てません(配ったアプリの中身は誰でも覗けるからです)。こうしたアプリでは、リフレッシュトークンだけで新しいアクセストークンを取り直せてしまいます。

その場合、リフレッシュトークンが漏れると、攻撃者は自分で新しいアクセストークンを何度でも作れます。アクセストークンを短くした意味が、まるごと無くなります。アクセストークンの写しは期限で止まりますが、リフレッシュトークンの写しは、誰かが気づいて止めるまで止まりません。

そこで窓口の側には、リフレッシュトークンが盗まれても被害を広げないための仕組みが求められます。OAuth のセキュリティの指針(RFC 9700)は、ブラウザやスマホのアプリに出すリフレッシュトークンについて、次の「入れ替え」か、鍵の持ち主に縛る方式(DPoP など)のどちらかを求めています。

  • 認証のしくみ ― 3つのトークン
  • 17 / 25
  • リフレッシュトークンが漏れると、何度でも取り直される
  • 人
  • 攻撃者
  • 守られている
  • 認可サーバー
  • 盗んだトークンを持つ
  • 盗んだリフレッシュトークン
  • 新しいアクセストークン
  • login.example(ログインの窓口)
  • 更新
  • 盗んだリフレッシュ
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • サーバー
  • API
  • アクセストークンの写しは期限で止まる。
  • api.example(写真を返す)
  • リフレッシュトークンの写しは、止めるまで止まらない
  • ひらめき
  • 期限の短いアクセストークンを、長いあいだ作り続けられる

使うたびに入れ替える。古いほうはもう通らない

リフレッシュトークンの入れ替え(ローテーション)では、リフレッシュトークンを使うたびに、新しいアクセストークンと一緒に新しいリフレッシュトークンが返ってきます。古いほうはそこで使用済みになり、二度と通りません。

窓口は、同じログインから続く一連のリフレッシュトークンを1つの系列として覚えていて、「いまの1枚」がどれかを知っています。アプリは、毎回届く新しい1枚に差し替えて持ち続けます。

これだけだと、盗んだ人が持ち主より先に使えば、盗んだ人のほうが「いまの1枚」を手に入れてしまいます。そこで次のページの仕組みと組み合わせます。

  • 認証のしくみ ― 3つのトークン
  • 18 / 25
  • 使うたびに入れ替える。古いほうはもう通らない
  • 更新
  • リフレッシュトークン 1
  • 使用済み
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • 更新
  • リフレッシュトークン 2
  • 使用済み
  • トークン
  • 状態
  • 系列
  • 1
  • 使用済み
  • F-17
  • 更新
  • リフレッシュトークン 3
  • いまの1枚
  • 2
  • 使用済み
  • F-17
  • 3
  • 有効
  • F-17
  • ひらめき
  • 窓口は「同じ系列のどれが今の1枚か」を覚えている

使用済みが届いたら、盗まれたとみなして系列ごと止める

入れ替えをしていると、持ち主と攻撃者が同じ1枚を持っている状態は長く続きません。どちらかが先に使えば、もう一方が持っているのは使用済みの1枚になります。

そこで窓口は、使用済みのリフレッシュトークンが届いたら、盗まれたと判断して、その系列をまるごと取り消します。図では、攻撃者が使用済みの 1 を出したので、いまの 3 も取り消しになりました。

持ち主もログインし直しになりますが、攻撃者を締め出せます。どちらが本物か窓口には分からないので、両方とも止めるのが安全な側の倒し方です。Auth0 や Supabase Auth など、多くの窓口がこの検出を備えています。

  • 認証のしくみ ― 3つのトークン
  • 19 / 25
  • 使用済みが届いたら、盗まれたとみなして系列ごと止める
  • 更新
  • リフレッシュトークン 1
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • 更新
  • リフレッシュトークン 2
  • トークン
  • 状態
  • 系列
  • 1
  • 使用済み
  • F-17
  • 更新
  • リフレッシュトークン 3
  • NG
  • 取り消し
  • 2
  • 使用済み
  • F-17
  • 3
  • 取り消し
  • F-17
  • 人
  • 攻撃者
  • 使用済みの 1 を出した
  • ひらめき
  • 持ち主もログインし直しになる。それでも攻撃者を締め出せる

IDトークンで API を呼んではいけない

ここまでくると、よくある間違いが見えてきます。IDトークンをアクセストークンの代わりに API に送ることです。どちらも JWT で、どちらにも sub が入っているので、つい使えそうに見えます。

しかし IDトークンの aud はアプリ(photo.example)で、API 宛てではありません。API が aud を確かめていれば、401 で断るのが正しい動きです。別のアプリ宛ての IDトークンを持ち込まれても通してしまう、という穴を作らないためです。

JWT 形式のアクセストークンには、ヘッダーの typ を at+jwt にする決まりもあります(RFC 9068)。API がこれも確かめれば、IDトークンを取り違える余地はさらに減ります。

  • 認証のしくみ ― 3つのトークン
  • 20 / 25
  • IDトークンで API を呼んではいけない
  • IDトークン aud: "photo.example"
  • API が待つもの aud: "https://api.example"
  • 守られている
  • 認可サーバー
  • → 合わないので 401 で断る
  • login.example(ログインの窓口)
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • NG
  • Bearer IDトークン
  • サーバー
  • API
  • 書類
  • IDトークン
  • api.example(写真を返す)
  • ひらめき
  • 宛先(aud)がアプリなので、API は断らなければならない

漏れて一番危ないのは、リフレッシュトークン

ここまでを「漏れたらどうなるか」で並べ直すと、図のようになります。

いちばん危ないのはリフレッシュトークンです。長く生き、何度でも新しいアクセストークンに換えられるので、止めるまで被害が続きます。次がアクセストークンで、期限が来るまで、あなたとして API を使われます。

IDトークンは、正しく作られたシステムでは API の鍵にはならないので、漏れて起きるのは主に名前やメールアドレスが読まれることです。ただし、前のページの間違い(API が IDトークンを受け入れている)があると、アクセストークンと同じ危なさに変わります。

  • 認証のしくみ ― 3つのトークン
  • 21 / 25
  • 漏れて一番危ないのは、リフレッシュトークン
  • 更新
  • リフレッシュトークン
  • 止めるまで、取り直し放題
  • 鍵
  • アクセストークン
  • 期限まで、API を使われる
  • 書類
  • IDトークン
  • 名前やメールを読まれる
  • IDトークンが危なくなるのは、間違って API が受け入れているとき(前のページの誤り)
  • ひらめき
  • 長く生き、何度でも新しいアクセストークンに換えられる

長く生きる鍵ほど、ブラウザから遠ざける

扱い方の基本は「長く生きるものほど、手の届かない場所に置く」です。

ブラウザの localStorage は便利ですが、ページの中で動く JavaScript なら何でも読めます。ページに悪意のあるスクリプトが紛れ込む(XSS)と、そこにあるトークンはそのまま持ち出されます。期限の短いアクセストークンなら被害も短く済みますが、リフレッシュトークンを置くと、被害は止めるまで続きます。

リフレッシュトークンは、アプリのサーバー側に置くか、ブラウザに置くなら HttpOnly の Cookie にします。HttpOnly を付けた Cookie は JavaScript から読めません。HTTPS でだけ送る Secure と、よそのサイトからの送信を絞る SameSite も付けます。どのトークンも、URL やログには書き出しません。

  • 認証のしくみ ― 3つのトークン
  • 22 / 25
  • 長く生きる鍵ほど、ブラウザから遠ざける
  • トークン
  • 置く場所
  • 置かない場所
  • アクセストークン
  • メモリ(変数)。期限が短いので被害も短い
  • localStorage・URL・ログ
  • リフレッシュトークン
  • サーバー側、または HttpOnly の Cookie
  • localStorage・JavaScript から読める場所
  • IDトークン
  • 確かめたら捨てる(セッションに置き換える)
  • API への送信
  • HttpOnly の Cookie は JavaScript から読めない。Secure(HTTPS だけ)と SameSite も付ける
  • ひらめき
  • localStorage は、ページに紛れ込んだスクリプト(XSS)から読める

ブラウザには、Cookie だけを渡す(BFF)

いまブラウザのアプリに勧められている形が BFF(Backend for Frontend)です。アプリの側に小さなサーバーを1つ置き、トークンはすべてそのサーバーが持ちます。

ブラウザに渡るのは、HttpOnly のセッションの Cookie だけです。ブラウザがアプリのサーバーに頼むと、サーバーが自分の持っているアクセストークンを付けて API を呼び、期限が切れればサーバーがリフレッシュトークンで取り直します。ブラウザの JavaScript には、どのトークンも一度も渡りません。

IETF のブラウザ向けアプリの指針(草案)も、いちばん安全な形としてこの BFF を挙げています。サーバーを置けない場合でも、少なくともリフレッシュトークンの入れ替えを使い、アクセストークンはメモリ(変数)にだけ置く、というところまでは守ります。

  • 認証のしくみ ― 3つのトークン
  • 23 / 25
  • ブラウザには、Cookie だけを渡す(BFF)
  • アプリの側に小さなサーバー(Backend for Frontend)を置く。
  • 守られている
  • 認可サーバー
  • ブラウザに渡るのは HttpOnly の Cookie だけ
  • login.example(ログインの窓口)
  • 鍵
  • アクセストークン
  • 人
  • ブラウザ
  • 更新
  • リフレッシュ
  • あなた
  • アプリ
  • ブラウザ
  • photo.example
  • Bearer アクセストークン
  • サーバー
  • API
  • 錠
  • セッションの Cookie
  • api.example(写真を返す)
  • ひらめき
  • トークンはアプリのサーバーが持ち、API にはサーバーから見せる

ログアウトでは、リフレッシュトークンを窓口で取り消す

ログアウトでやることは3つです。窓口の取り消しの口(revocation、RFC 7009)にリフレッシュトークンを送って無効にすること。アプリ自身のセッションを消すこと。ブラウザの Cookie を消すことです。

リフレッシュトークンは台帳にあるので、取り消せばその場で使えなくなります。一方、手元に残ったアクセストークンは、JWT であれば期限まで通ります。図では、残りの9分は API に通ってしまいます。

ここでも、アクセストークンの期限を短くしておく理由が効いてきます。「ログアウトしたのに、しばらく使える」時間を短くできるのは、期限の長さだけです。

  • 認証のしくみ ― 3つのトークン
  • 24 / 25
  • ログアウトでは、リフレッシュトークンを窓口で取り消す
  • アプリのセッションも消し、ブラウザの Cookie も消す
  • 守られている
  • 認可サーバー
  • login.example(ログインの窓口)
  • 取り消し(revoke)
  • 人
  • ブラウザ
  • あなた
  • アプリ
  • 更新
  • リフレッシュ
  • NG
  • ブラウザ
  • photo.example
  • 鍵
  • アクセストークン
  • 砂時計
  • 残り9分は通る
  • サーバー
  • API
  • api.example(写真を返す)
  • ひらめき
  • 取り消しても、手元のアクセストークンは期限まで通る。だから短く

確認:誰が出し、誰に見せ、誰が確かめるか

3つとも、出すのは窓口(認可サーバー)です。違うのは、見せる相手と、確かめる人です。

IDトークンは、アプリが「誰がログインしたか」を知るためのもので、アプリが確かめ、確かめたらアプリのセッションに置き換えます。アクセストークンは、API が「使ってよいか」を判断するためのもので、API が確かめます。期限は短く、持っている人なら誰でも使えます。リフレッシュトークンは、アクセストークンを取り直すためのもので、窓口だけが確かめます。長く生きるので、漏れたときに一番危なく、入れ替えと取り消しで守ります。

扱い方は「長く生きるものほど、ブラウザから遠ざける」「IDトークンを API に送らない」「署名だけでなく aud と exp まで確かめる」の3つを覚えておけば、大きく外すことはありません。

  • 認証のしくみ ― 3つのトークン
  • 25 / 25
  • 確認:誰が出し、誰に見せ、誰が確かめるか
  • 項目
  • IDトークン
  • アクセストークン
  • リフレッシュトークン
  • 見せる相手
  • アプリ
  • API
  • 窓口(認可サーバー)
  • 確かめる人
  • アプリ
  • API
  • 窓口
  • 何のため
  • 誰がログインしたか
  • API を使ってよいか
  • 取り直すため
  • 形
  • 必ず JWT
  • JWT か ただの乱数
  • たいてい ただの乱数
  • 期限の目安
  • 数分〜1時間
  • 5分〜1時間
  • 数日〜期限なし
  • 漏れたら
  • 名前やメール
  • 期限まで悪用
  • 止めるまで悪用
  • ひらめき
  • 出すのはいつも窓口。見せる相手と確かめる人は、トークンごとに違う

0 件のコメント

読み込み中…

権利侵害・不適切な内容の連絡: [email protected]