Token Exchange(RFC 8693)
現代のマイクロサービス構成では、ユーザの代理として他のサービスを呼ぶ 場面がほぼ必ずあります。frontend がトークンを取り、service A がそれを受け取り、service A が service B を呼ぶ。service A はユーザのトークンをそのまま使うべきか、新しく取り直すべきか、両方を組み合わせるべきか? RFC 8693 はこのトークン交換の通信路上の形式を定義します。
設計上、2 つの意図を本質的に区別します:
- Impersonation(なりすまし) — service A はユーザのトークンをそのまま service B に提示する。ユーザ本人として ふるまう。service B からは
sub=aliceに見え、下流の監査ログには Alice 単独の操作として記録される。 - Delegation(委譲) — service A は OP に対し新しいトークンの発行を依頼する。
subはやはり Alice だが、actclaim に{sub: service-a}が入り、Alice の権限が service A 経由で行使されていることを記録する。service B からはsub=alice, act={sub: service-a}に見えて、chain に依存するポリシー判定(「service A は Alice の振込を実行してよいが、リクエスト元が Alice 自身からの場合に限る」)が可能になる。
近年の脅威モデルでは委譲(delegation)のほうが望ましいとされています。監査の連鎖が途切れず、失効を中継 service 単位で当てられ、最小権限の境界が subject 全体に広がるのではなく actor 単位で絞り込めるためです。
このページで触れる仕様
用語の補足
- subject_token — 新トークンの
subを構成する identity の保持者となるトークン。通常は上流から転送されてきたユーザのアクセストークン。 - actor_token — 交換を実行する呼び出し元(service)を識別するトークン。存在すると新トークンには
actclaim が付与され、actor のsub/client_idがそこに包まれる。 actclaim(RFC 8693 §4.1) —subの代わりに行動している主体を記録する入れ子オブジェクト。連鎖でき(act.act.act…)、4 ホップの呼び出しなら 4 つの中継 service をすべて記録できる。cnfの再バインド — 発行トークンのcnf(RFC 7800 confirmation)は、subject ではなく 呼び出し actor の DPoP / mTLS proof に設定される。トークンは交換を実行する service に対して送信者制約が掛かる(sender-bound)。
なりすまし(Impersonation)と委譲(Delegation)を並べて見る
actor_token だけです。パラメータ 1 つの差で、監査ログが「alice が行った」と記録するか「alice が service A を経由して行った」と記録するかが変わります。差は監査ログ(service B が呼び出しの起点を知れる)と認可ポリシー(センシティブな操作で actor を要求できる)に現れます。
OP が強制すること
仕様がデプロイ側のポリシーに委ねている項目のうち、本ライブラリの RFC 8693 ハンドラはいくつかについて独自の方針を採っています:
actchain は OP 側で構築する。呼び出し側が手で渡すのではない。actor_tokenがsubject_tokenと異なる場合、OP は actor の検証済み credential からactを構成する。呼び出し側にactを捏造させない。- audience は正規化して許可リストで制限する。RFC 8707 の
audience/resourceパラメータは policy の評価前に正規化します。両方を省略した場合は subject token の audience を既定値として使います。得られた audience はすべて client が認可されていなければなりません。 - scope は積集合(intersection)。和集合(union)ではない。発行トークンの scope は(要求 scope、
subject_tokenの scope、呼び出し client の許可リスト)の積集合。RFC 8693 §3.1 は scope の縮小は許すが、OP は scope の膨張(inflation)を禁止する。 - TTL は上限で頭打ちにする。(handler 希望、
subject_tokenの残り寿命、OP のグローバル上限)の最小値まで切り詰められる。長寿命トークンをさらに長いトークンへ伸ばすロンダリングはできない。 cnfは呼び出し actor に再バインドする。service A が交換リクエストに DPoP proof を提示すれば、発行トークンのcnf.jktは service A の DPoP 鍵に一致する — ユーザのものでもsubject_tokenのものでもない。service B は新トークンのcnfに対して DPoP proof を検証する。
これらは組み込み側が提供する TokenExchangePolicy が呼ばれる 前 に強制されます。ポリシー側はさらに絞り込むこと(特定 audience を拒否、特定の actor 組み合わせを拒否など)はできますが、広げることはできません — OP が計算したデフォルトが下限です。
resource と audience の正規化
OP は、token exchange とその他の resource indicator を扱う箇所で、URI 形式の resource / audience を同じ比較形へ正規化します。URI 形式の値は scheme と host を持つ絶対 URI でなければなりません。OP は scheme と host を小文字化し、既定 port(http の 80、https の 443)を取り除き、path 末尾の slash を取り除きます。path の大文字・小文字は保持し、query と既定でない port は入力どおり保持します。fragment と userinfo は拒否するため、https://[email protected]/api や https://api.example.com/api#v1 は許可リストの値と一致しません。
たとえば https://API.Example.com:443/v1/?tenant=acme は https://api.example.com/v1?tenant=acme として比較されます。リクエストが audience と resource の両方を省略した場合は、subject token の audience を同じ規則で正規化して引き継ぎます。URI 形式ではない logical audience label(URN を含みます)は byte 完全一致で比較し、OP は小文字化などの書き換えを行いません。
実際に必要になるとき
Token exchange が 不要 な場合:
- 両 service を同じチームが所有し暗黙に信頼している。ユーザのトークンをそのまま multi-
audで使う。 - 下流 service が上流 actor を知る必要がない。ユーザのトークンを適切な
audで受け渡す。
Token exchange が 必要 な場合:
- 下流 service が chain に誰がいるか に応じてポリシーを適用する — 「振込 service はモバイルアプリを信頼するが SMS bot は信頼しない、Alice として呼ばれていても」。
- chain にサードパーティ service が含まれていて、cross-org actor の記録が audit 義務として必要。
- Sender-binding(DPoP / mTLS)を、ユーザではなく 呼び出し service に追従させたい。
動かしてみる
examples/33-token-exchange-delegation は frontend → service-a → service-b の chain を実演します。frontend がユーザトークンを取得、service-a がそれを委譲付きトークン(act={sub: service-a} を伴う)に交換、service-b の RS 側 verifier が act.sub をたどって委譲付きトークンのみを受理します。
(cd examples/33-token-exchange-delegation && GOWORK=off go run -tags example .)example はロール別ファイルに分割されています(op.go で OP の組み立て + TokenExchangePolicy、service_a.go で中継、service_b.go で resource server、probe.go で self-verification)。
続きはこちら
- 使い方: token-exchange の組み込み —
op.RegisterTokenExchange、TokenExchangePolicyの契約、audience の構成、op.PtrBool(true)による refresh 発行のオプトイン。 - Custom Grant の組み込み — token exchange は OP がルーティングする「カスタム grant_type」の同梱例。独自 URN を書く組み込み側は
op.WithCustomGrant経由で同じ形をたどる。