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)を並べて見る
差は監査ログ(service B が呼び出しの起点を知れる)と認可ポリシー(センシティブな操作で actor を要求できる)に現れます。
OP が強制すること
仕様がデプロイ側のポリシーに委ねている項目のうち、本ライブラリの RFC 8693 ハンドラはいくつかについて独自の方針を採っています:
actchain は OP 側で構築する。呼び出し側が手で渡すのではない。actor_tokenがsubject_tokenと異なる場合、OP は actor の検証済み credential からactを構成する。呼び出し側にactを捏造させない。- audience は明示 + 許可リスト必須。RFC 8707 audience(
audience/resourceパラメータ)は正規化され、このクライアントがターゲットにできる audience はポリシーで決める。 - 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 が計算したデフォルトが下限です。
実際に必要になるとき
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 && 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経由で同じ形をたどる。