Transaction Tokens とは何か——認可の文脈は三ホップのどこで消えるか
Transaction Tokens(draft -11)を精読し、LayerX の Capability Assertion と並べる。エージェント→ゲートウェイ→プロバイダの三ホップで、認可の文脈がどこで抜け落ちるかを表で示します。

認可の文脈は、Trust Domain の境界で必ず消えます。その手前で消すかどうかは、設計次第です。 Trust Domain とは「共通のセキュリティ統制とポリシーを共有するシステムのまとまり」(ドラフト §3)で、 自社のマイクロサービス群は通常これ一つに収まります。Transaction Tokens(Txn-Token)は IETF の OAuth ワーキンググループが標準化中の短命の署名付き JWT で、一つの Trust Domain の内側で「誰のための、どの取引の、 何の目的の処理か」をリクエストと一緒に各ホップへ運びます。 LayerX の tkmt 氏が Zenn に書いた記事(はてブで注目)は、 それが解く問題を一文で定式化しています——「入口で行ったユーザー検証の事実と認可の文脈が、ホップの途中で 消えてしまう」。
エージェント基盤も同じ形で、ホップは三つ。エージェントの実行基盤(harness)→ LLM ゲートウェイ → モデル提供者。
前作「エージェントに渡す鍵の形」が扱ったのは鍵の形(短期・スコープ・上限)。本篇が
扱うのは、鍵とは別にリクエストと一緒に旅をしなければならない文脈——誰のためか(sub)、どの取引か(txn)、
何の目的か(scope)、どの予算から引くか(tctx)。API キーは鍵の持ち主を証明しますが取引は証明しません。
以下、ドラフト(draft-ietf-oauth-transaction-tokens-11、2026-07-30、work in progress)と LayerX の記事の精読です。
「私は判定を済ませた」という自己申告しか残らない——LayerX が定式化した問題
題材は、承認フローを持つ「バクラク申請」と、申請に紐づく機微情報を保管する別プロダクト(記事では「サービス X」)の 二段です。承認画面を開くと、(1) バクラク申請が承認ルートや代理設定から「この承認者は閲覧してよい」と判定し、(2) サービス X の RPC で機微情報の実体を取る。判定材料は (1) にしか無く、承認者はサービス X の権限を持っていないかもしれない。記事は 四つの選択肢を全部却下しており、その理由がそのまま多段ホップ認可の教科書です。
| サービス X の守り方 | 何が壊れるか(記事の理由) |
|---|---|
| permission を要求しない | 承認と無関係なユーザーでも、リソース ID を指定して直接取得できてしまう |
| permission を要求する | 承認者はサービス X の権限を持っていないので、正規の承認画面まで弾かれる |
| サービス X で判定をやり直す | 判定材料はバクラク申請にしか無く、同じ判定を繰り返す。dataloader 経由で何重にも呼ばれるため性能もつらい |
| バクラク申請からの呼び出しを無条件に信頼する | 「バクラク申請を経由した」ことをサービス X が確かめる手段がなければ、ただの自己申告になる |
四行目が核心です。途中のサービスから見ると「呼び出し元の『私は正しい判定を済ませたので信頼してほしい』という 自己申告を信じるしかありません」(記事)。届くのは呼び出し元のサービスアイデンティティだけで、「誰が呼んだか」であって 「何のためか」ではない。
エージェント基盤に読み替えると、バクラク申請の位置に harness、サービス X の位置にモデル提供者、間にゲートウェイ。 harness は「このユーザーの、このタスクのために、このモデルを呼んでよい」と判定済みなのに、API キー一本で上流を呼ぶ構成 (OpenAI 互換 API の一般形)では、ゲートウェイに届くのも上流に送るのも API キー一本。自己申告が二段連なっています。
Transaction Tokens とは何か——IETF ドラフト -11 を精読する

ドラフトは本稿執筆時点(2026-08-25)で
2026-07-30 付の draft-ietf-oauth-transaction-tokens-11 が最新、Intended status は Standards Track、有効期限
2027-01-31。ドラフト自身が「work in progress 以外の形で引用するのは不適切」と書いているので、以下は作業中の
文書を読んだ記録です。Introduction は、内部ワークロードが侵害されたときに可能になる不正を四つ列挙しています。原文のまま。
- Invocation of workloads in the absence of a valid transaction.
- Arbitrary user or workload impersonation.
- Parameter modification or augmentation.
- Theft of tokens used to convey authorization context (e.g. OAuth 2.0 access tokens).
一つ目が LayerX の「ID を指定すれば取れてしまう」、三つ目が「途中で書き換えられていないこと」に対応し、二つ目と四つ目は 偽装とトークン窃取そのものです。 Txn-Token の定義は §4 の一文で足ります——「short-lived, signed JWTs that assert the identity of a user or a workload and an authorization context」。認可の文脈とは §4.1 によれば認可・会計・監査に使う情報で、その要は「取引の意図または目的」 をできる限り狭く定義すること。会計(accounting)という語は、後で予算の話に戻ってきます。
発行と伝搬——外部エンドポイントが交換し、内側は同じトークンを持ち回る
発行者は Transaction Token Service(TTS)という専用サービスで、Trust Domain ごとに論理的に一つ(§3、§10 の MUST)。 ドラフトの Figure 1 を素の形で描き直したのが図 2 で、交換は外部エンドポイント(API ゲートウェイ等)が入口で一度だけ行い、 以降の内部ワークロード間の呼び出しは同じ Txn-Token を使います。

TTS への要求は、HTTP 経由なら RFC 8693(OAuth 2.0 Token Exchange)の
プロファイルでなければなりません(§5.1、§11 の MUST)。requested_token_type に本ドラフトが新設する
urn:ietf:params:oauth:token-type:txn_token、subject_token に入口で受けたトークンなど。request_context と
request_details は RECOMMENDED。前作の OBO(On-Behalf-Of)や WIF(Workload Identity Federation)と同じ器です。
伝搬には専用の HTTP ヘッダ Txn-Token を使い、Authorization は使ってはならない(§12。サービス間の認可に使われて
いる可能性があるから、と IANA 節 §16.4)。受け取ったワークロードの検証は三つだけです(§12.2)。JWS 署名、aud が自分の
Trust Domain を指すこと、期限内であること。加えて、下流への呼び出しには受け取ったままの Txn-Token を付ける(MUST)。
claims——REQUIRED 七つ、RECOMMENDED 二つ、OPTIONAL 一つ
JWT ヘッダの typ は txntoken+jwt 固定(MUST)。本体の claims を §9.2 の要否と一緒に並べます。
| claim | 要否 | 意味(§9.2 の要約) |
|---|---|---|
iat | REQUIRED | 発行時刻 |
aud | REQUIRED | このトークンが有効な Trust Domain。その外では受け付けてはならない |
exp | REQUIRED | 有効期限 |
txn | REQUIRED | 取引の一意識別子(RFC 8417 §2.2 の定義) |
sub | REQUIRED | 取引の主体。aud の Trust Domain 内で一意。OpenID Connect と違い iss に紐づかない |
scope | REQUIRED | この取引の目的を「できる限り狭く」。TTS が決め、要求値や外部トークンの scope と一致する必要はない |
req_wl | REQUIRED | Txn-Token を要求したワークロードの識別子 |
tctx | RECOMMENDED | 取引の認可の詳細。Call Chain(一連の内部呼び出し)の間不変であるべき値。TTS が決める |
rctx | RECOMMENDED | リクエストの環境文脈(発信元 IP、認証方式、トランスポートなど) |
iss | OPTIONAL | 一つの Trust Domain に束縛され署名鍵も既知のことが多いため必須ではない。鍵が事前に共有されない構成では §13.16 が使用を認める |
なお、検索で azd や purp という claim 名を見た方へ。Document History によれば azd は -04 で tctx に、
purp は -07 で scope に改名されています(各版の全文で確認)。現行 -11 にその名前はありません。
ドラフトの Figure 4(非規範例)は「US の VIP 顧客が MSFT を 100 株買う」取引です。コメントも含めて原文のまま
置きます(// コメント付きなので、そのままでは JSON として不正です)。
{
"iat": 1686536226,
"aud": "trust-domain.example",
"exp": 1686536586,
"txn": "97053963-771d-49cc-a4e3-20aad399c312",
"sub": "d084sdrt234fsaw34tr23t",
"req_wl": "apigateway.trust-domain.example", // the internal entity that requested the Txn-Token
"rctx": {
"req_ip": "69.151.72.123", // env context of external call
"authn": "face" // from RFC 8176
},
"scope" : "trade.stocks",
"tctx": {
"action": "BUY", // parameter of external call
"ticker": "MSFT", // parameter of external call
"quantity": "100", // parameter of external call
"customer_type": { // computed value not present in external call
"geo": "US",
"level": "VIP"
}
}
}exp - iat = 360、つまり 6 分。§6 は寿命を「on the order of minutes or less」と書き、期限切れの subject_token からの
発行を禁じています(MUST NOT)。もう一点、tctx の customer_type は外部呼び出しに無く、TTS が内部データから計算して
足した値です。文脈は「入口で受け取ったもの」だけでなく「入口で確定させたもの」を運ぶ器だと分かります。
Txn-Token が「してはいけない」こと——§13 の六つの禁止
LayerX の記事は自ら「ざっくりとした概要で、詳細には触れていません」と断っています。設計に直接響く詳細は §13 Security Considerations の禁止事項で、読み落とすと Txn-Token を「便利な内部 JWT」として使い始めてしまいます。§ 番号は -11 のもので 改版で動くので、監査資料には条番号ではなく要件の文言で引いてください。
| § | 禁止(原文の MUST / MUST NOT) | なぜ |
|---|---|---|
| 13.4 | Txn-Token に、外部エンドポイントが受けたアクセストークンを含めてはならない | 抜き出して任意のリソースサーバーで再生できる。Txn-Token の期限は守ってくれない |
| 13.6 | 要求された scope は元の subject_token の scope 以下 | 域内の認可モデルは外部の OAuth scope と別物になりがちで、意図しない拡大が起きる |
| 13.14 | subject_token の scope が判定できないなら拒否。不明を「無制限」と扱わない | 拡大していないことを保証できないから |
| 13.12 | Txn-Token でワークロードが自分を認証してはならない | 認可の文脈であって身元証明ではない。認証は別建て(ワークロードと TTS 間は §11.5、ワークロード間は仕様外) |
| 13.13 | Txn-Token を OAuth 2.0 アクセストークンとして使ってはならない | アクセストークンは「クライアントへの権限の付与」、Txn-Token は「特定の取引の文脈」 |
| 13.15 | 再発行(Txn-Token を subject_token に)では txn・sub・aud を変えず、scope を拡げず、期限切れからは発行しない | 置換は Txn-Token の存在意義を打ち消しかねない |
15.2 も足しておきます。Txn-Token をそのままログに書いてはならない(MUST NOT)。残すのはハッシュか、署名を外した
ペイロード。rctx.req_ip は多くの法域で個人情報です(15.1)。そして本篇の結論に直結するのが §14 です。Txn-Token は aud の Trust Domain の内側でしか有効ではない。別の
Trust Domain に文脈を届けたいなら別ドラフト(OAuth Identity and Authorization Chaining Across Domains)を使うべし
(SHOULD)、とだけ書いてあります。域内の道具であり、境界で別の仕組みへ引き継ぐ設計です。
Capability Assertion と Txn-Token を同じ表に——LayerX の独自実装は何を持ち、何を持たないか
LayerX は同じ問題を、社内で Capability Assertion と呼ぶ仕組みで解いています。記事の定義は「認可判定を行ったサービスが 発行する短命の署名付きトークンで、『どのサービスが、どのテナント・ユーザーについて、何のリソースの閲覧を判定済みか』 を後続のサービスが検証できる仕組み」。実体は protobuf メッセージで、記事が JSON 化しています。
{
"issuer_service": "workflow.v1.RequestService",
"tenant_id": "tenant-123",
"tenant_user_id": "user-456",
"resources": [
{
"field": "request_id",
"resource_ids": ["req-001", "req-002"]
}
],
"issued_at": 1755672000,
"expires_at": 1755672180,
"token_type": "capability_assertion"
}expires_at - issued_at = 180 秒。受け取り側の RPC には proto のカスタムオプションで発行元と TTL を宣言し、共通の
interceptor が発行も検証も行うので、ハンドラには一行も書かない、と記事にあります(以下は受け取り側の抄録、注釈は筆者訳)。
// Receiving side: the RPC that returns sensitive data declares
// which issuers it trusts and which request field to match.
rpc BatchGetRequestDetails(...) returns (...) {
option (auth.v1.rpc) = {
capability_assertion_policy: {
allowed_issuers: ["workflow.v1.RequestService"]
request_resource_id_field: "request_ids"
max_ttl_seconds: 180
}
};
}二つを項目ごとに並べます。対応の基準は「Txn-Token の当該 claim に相当するものがあるか」です。
| Txn-Token の claim | Capability Assertion | 対応 |
|---|---|---|
sub(取引の主体) | tenant_id + tenant_user_id | テナント付きで、むしろ具体的 |
req_wl(要求したワークロード) | issuer_service | 経路の証明として同じ役割 |
exp / iat | expires_at / issued_at(180 秒、max_ttl_seconds で受け側が上限) | ドラフト例の 360 秒より短い |
aud(Trust Domain) | 明示の claim なし。受け側が allowed_issuers で発行元を限定 | 域の識別子ではなく発行元リストで代替 |
txn(取引 ID) | なし | 監査・リプレイ検知のための一意 ID は記事の JSON に無い |
scope(取引の目的) | なし。token_type は種別のみ | 「閲覧」に用途が固定されているため不要、と読める |
tctx(不変の認可詳細) | resources[].resource_ids | 「判定済みのリソース ID」だけ。処理内容やパラメータは運ばない |
rctx(環境文脈) | なし | |
| 発行者 | 認可基盤(発行者とアクセストークン由来のユーザー情報を書き込み署名) | TTS と同じ「一箇所で署名」 |
| 伝搬 | レスポンスで返し、次のリクエストに添付 | Txn-Token は同一トークンを持ち回る。こちらは発行 RPC の応答経由 |
記事自身の整理は「Txn-Token が伝搬する内容は処理内容そのもの、Capability Assertion は認可判定済みのリソース ID のみ」。
更新処理の不変性まで保証したいのがドラフト、参照の判定済みを伝えるだけが要件だったのが LayerX。方向性は揃っていたと
記事は安堵していますが、私の読みでは表の ✗ 二つ(txn と scope)がエージェント基盤では効いてきます。参照専用なら
要らない二つが、「呼び出しごとに課金が発生する処理」では要る。
エージェント → ゲートウェイ → プロバイダ:三ホップで何が抜け落ちるか
運びたい文脈は四つ。誰のためか(sub)、どの取引か(txn)、何の目的か(scope)、どの予算から引くか
(tctx に置く値)。鍵の形は (a) 無制限の長期 API キー(制限付きキーは前作の表の ⚠ に相当)、(b) 短期・スコープ付き
キー(OBO / WIF で交換したもの)、(c) Txn-Token 型の文脈をキーとは別に運ぶ、の三種。(b) は鍵の話、(c) は文脈の話——
前作と本篇の分業です。
表を読む前に、境界の位置を一つ直しておきます。harness は顧客側のソフトウェアで、ゲートウェイ事業者と「共通の
セキュリティ統制」を共有していません。Figure 1 でいえば External client、つまりホップ 1 も Trust Domain の境界です。
Txn-Token が生まれるのは入口——ゲートウェイの外部エンドポイントが API キーを subject_token にして自域の TTS と交換した
瞬間——で、harness との間にはまだ存在しません。だから (c) 列のホップ 1 は「入口で何が、何を出所として確定するか」を
読みます。harness とゲートウェイが同じ組織なら、ホップ 1 は域内になりこの区別は消えます。

| 文脈 | (a) 長期 API キー(無制限) | (b) 短期・スコープ付きキー | (c) Txn-Token 型の文脈 |
|---|---|---|---|
| ホップ 1:harness → ゲートウェイ入口(境界) | |||
誰のためか(sub) | キーの持ち主(組織かプロジェクト)まで | OBO なら act(actor).sub に代理者、sub に本人。WIF はワークロードの身元 | 入口の TTS が確定して署名。出所は三択——ユーザー単位の鍵、harness の申告、(b) の OBO。保証は入口以降の不変性で、申告の真偽ではない |
どの取引か(txn) | 交換トークンに取引 ID は無い | 入口の TTS が生成。ログの相関キー | |
何の目的か(scope) | scope は鍵の中にあるが「何をしてよいか」であって「今回何をするか」ではない | TTS が取引単位に狭く決める。鍵の scope が §13.14 の trusted source | |
| どの予算から引くか | 請求書で気づく | 鍵単位の上限は設計可能。取引単位は無い | 鍵に束縛された予算 ID と上限を TTS が書く(Figure 4 の customer_type と同型) |
| ホップ 2:ゲートウェイ内部(ルータ → 課金 → 上流アダプタ。自社なら内部サービス A → B → C) | |||
| 四つとも | キーの持ち主 ID を引き回すか、ヘッダで自己申告 | 検証結果を内部ヘッダに書けば LayerX の「自己申告」。検証済みトークンをそのまま転送すれば署名は残る | 同一 Txn-Token を持ち回り、各ワークロードが署名・aud・exp を検証 |
| ホップ 3:ゲートウェイ → モデル提供者(境界) | |||
| 誰のためか | 上流キーの持ち主=ゲートウェイ事業者 | WIF ならゲートウェイのワークロード身元まで | 域外で無効(§14)。渡すべきでもない——域内で再生でき(§13.1)、req_ip は個人情報(§15.1) |
| どの取引か | 提供者の request ID と txn をゲートウェイのログで結べる。提供者には届かない | ||
| 何の目的か | 提供者のマッピング権限(前作の表)で「どのモデル・どの操作」まで | 境界で提供者側の鍵の形に畳まれる | |
| どの予算から引くか | 提供者のプロジェクト単位 | 境界の手前で数え終えているのが正しい形 |
表から読めることは二つです。第一に、ホップ 2 はいちばん見落とされやすい場所です。入口で検証した結果を X-Org-Id の
ような内部ヘッダに書いて引き回すのは、LayerX の四つ目の却下案そのもの。ゲートウェイの Trust Domain に TTS を一つ置き、
入口で交換した Txn-Token を持ち回るのが、ドラフトの絵をそのまま当てた形です。ただし Txn-Token が固めるのは入口で確定した
値であって、sub を本当にエンドユーザーにしたいなら、鍵をユーザー単位で発行するか、(b) の OBO トークンを subject_token
に使うか。(c) は (b) の代わりではなく、(b) の上に載せるものです。
第二に、ホップ 3 は (c) でも ✗ が並びますが、これは仕様の前提です。モデル提供者はゲートウェイ事業者と別 Trust Domain。 文脈はここで必ず提供者側の鍵の形に畳まれ、届くのは前作の表の経路(WIF・auth キー・IAM)が運べる分だけ。設計上の 含意は逆向きになります——取引単位の判定と課金は、境界の手前、つまりゲートウェイの内側で完了させる。境界の向こうに 期待しない。
ゲートウェイが予算を取引単位で数えるなら、どの claim が要るか——設計としての JWT 例
ここは設計メモです。私たち(pirouter のゲートウェイ)も例外ではなく、以下は設計として書いているもので、出荷済みの
機能ではありません。担保するのは入口以降の不変性と、課金と監査が同じ取引 ID を見ること。担保しないのは harness の申告の
真偽と提供者ホップ(§14)。Figure 4 を、入口で API キーを subject_token にして交換した Txn-Token に置き換えると、
こうなります。フィールド名と値は例示です。
{
"iat": 1787616000,
"aud": "gateway.example",
"exp": 1787616300,
"txn": "3c9a1f7e-5b2d-4e8a-9f61-0d7c2b4a8e15",
"sub": "user:acme/7f3a",
"req_wl": "edge.gateway.example",
"rctx": {
"req_ip": "203.0.113.24",
"key_id": "key_01J6…"
},
"scope": "chat.completions",
"tctx": {
"model": "provider-x/model-y",
"max_output_tokens": 4096,
"budget": {
"id": "bud_2026-08_acme_agents",
"cap_usd_micro": 2000000
},
"task": "ticket-triage/INC-4821"
}
}なぜその位置に置くか。
subはエンドユーザー。API キーの持ち主(組織)ではなく、鍵に束縛された利用者、またはsubject_tokenにした OBO トークンの本人を、入口で解決して書きます。txnは取引 ID。無いと、リトライ・フォールバック・ストリーミングの再接続が全部別の呼び出しとして数えられます。 §13.2 の通り、ログに残せば監査の相関キーです。scopeは取引の目的。前作の OBO のscopeが「何をしてよいか」だったのに対し、こちらは「今回何をするか」。 §9.2.1 の通り鍵の scope より狭くしてよく、その鍵の scope を TTS が鍵 DB から引けることが §13.14 の前提です。tctx.budgetは予算の ID と上限であって、残高ではありません。tctxは Call Chain の間不変であるべき値の置き場 (§9.2.3)なので、可変の残高は書けない。課金ワークロードは ID で残高を引き、上限は署名された値と照合する分業です。 §13.10 も、入口のゲートウェイが取引金額を Txn-Token に持つ例を挙げています。cap_usd_microはマイクロ USD (2,000,000 = 2 USD)。tctx.model/max_output_tokens/taskは、Figure 4 のaction/ticker/quantityに相当する、入口で 固定した外部呼び出しのパラメータ。taskは harness の申告で、真偽は上の三択次第です。rctx.key_idは鍵の識別子で、鍵そのものではありません。§13.4 はアクセストークンを含めることを禁じており、 API キーも同じ理由で同じ扱い。トークンが漏れても鍵は漏れません。- モデル提供者には、このトークンのどれも送りません(§14、上の第二点)。上流アダプタは提供者側の鍵の形に畳み、
提供者が返す request ID を
txnの隣にログしておきます。
authn を入れていないのは意図的です。ドラフトの例の "authn": "face" は RFC 8176 の認証方式参照値で、API キー認証に
対応する値がそこに無い。無い値を発明するより、省きます。
請求書——Txn-Token を持ち込む費用
前作と同じく、費用を先に並べます。
- ワークロード認証は別建てで、先に要る。§13.12 の通り Txn-Token は認証の代わりにならず、内部サービス間の mTLS や SPIFFE などの身元基盤が前提。無い組織では、Txn-Token の前に別プロジェクト級の仕事があります。
- TTS を一つ建てる。Trust Domain ごとに論理的に一つ(§10)。署名鍵の配布とローテーション(§13.11)、複数インスタンス なら設定改竄への防御(§13.8)。前作の「認可サーバーを一つ足す」と同じ種類の請求書が、もう一枚。
- 入口に往復が一つ増える。交換は入口で一度なので内部ホップ数には比例しませんが、TTS が落ちれば入口が止まります。
- ログの設計をやり直す。トークンはそのまま書けず(§15.2)、
req_ipは個人情報(§15.1)。アクセスログに JWT を そのまま吐いている構成は、ここで手が入ります。 - リプレイは自力。§13.1 の通りリプレイ耐性は無く、単一使用チェック(§13.2)には共有状態が要ります。「同じ
txnを 二度受けない」を複数インスタンスで保証するのは、それ自体が分散システムの仕事です。
LayerX の Capability Assertion が示唆的なのは、発行と検証を既存の権限基盤と共通 interceptor に相乗りさせ、TTS 相当の 新設コストを抑えた点です。標準を待つか自前で作るかは、この相乗り先が社内にあるかどうかで決まります。
月曜にやる三つ
- 入口の認可結果を次のホップにどう渡しているかを一行で書く。「内部ヘッダに書いている」なら、LayerX の四つ目の 却下案と同じ形です。ゲートウェイを買う側なら同じ問いをベンダーへ——検証結果を内部でどう運んでいるか、署名付きか。
- 内部呼び出しの受け側が §12.2 の三点——署名・
aud・exp——のどれかを検証しているか確かめる。どれもしていないなら 問題は Txn-Token 以前で、内部ヘッダを署名付き JWT にするだけでも LayerX の最小形になります。買う側なら、アクセス ログに JWT や API キーをそのまま書いていないかを聞く(§15.2)。 - 取引 ID がどこで生成され、どこまで届いているかを追う。タスク単位で費用を出したいなら、その ID が課金ワークロード と提供者の request ID の両方と結べるログが要ります。無ければ取引単位の予算はまだ数えられていない——買う側にとっては これがベンダーへの最初の質問です。