Identity · Authorization · Distributed Systems

The Token at the Edge,
and the End of Inward-Facing Trust.

A note on framing

I'm reading draft-ietf-oauth-transaction-tokens-08 and trying to share my understanding in a simpler, shorter way. The draft is intentionally a framework — it defines concepts (transaction claims, requester context, workload identity) but leaves most implementation choices open.

To keep this readable, I commit to one concrete profile of the spec throughout. Specifically: JWT format, RFC 8693 token-exchange minting, a centralized TTS, trust-domain-wide audience, and short minute-scale lifetimes. None of these are normative. The draft permits CWT or other structured formats; direct or workload-initiated issuance; distributed or per-cluster TTS deployments; service-specific or multiple audiences; and any lifetime the implementer judges appropriate.

The claim names tctx and rctx are normative — defined in §10.2 of the draft and registered with IANA in §16.2 — so I use them throughout. Treat the wire formats below as one defensible profile of the spec, not as the only one.

There is a quiet lie at the heart of most production microservice architectures, and it sounds almost reasonable when you first hear it. The lie is this: once a request crosses the gateway, we trust it. The access token authenticated the user, the WAF inspected the payload, the rate limiter approved the flow — and so by the time the request reaches the order service or the patient service or the ledger, the request has earned its way in. The interior is friendly. The hard part is over.

It is not over. It has barely begun. A single external API call fans out, in any reasonably mature system, into dozens of internal workload invocations crossing trust boundaries that exist on paper and not much else. Each hop trusts the previous hop, which trusted the hop before it, which trusted the API gateway, which trusted the access token. The chain of trust is exactly that — a chain — and a chain has the strength of its weakest link, which today is whichever workload was compromised most recently through a supply-chain attack, a credential leak, a malicious dependency, or a bug nobody caught in code review.

The OAuth 2.0 access token was designed to solve a problem at the edge: how does a third-party client prove that a user delegated some authority to it? It does this beautifully. It was never designed to be the authorization primitive that propagates through a fifteen-hop internal call chain inside a trust domain. We made it do that because we had nothing else. The IETF oauth working group has now proposed something else, and it is worth understanding deeply.

This piece is about Transaction Tokens — Txn-Tokens, in the draft's notation — and the architectural correction they represent. I want to walk through what they are, what they actually solve, where they fit alongside SPIFFE and mTLS and the rest of the zero-trust toolkit, and why I think they will become quietly load-bearing in the next generation of internal security architecture. I will use a stock trade and a patient records query as the worked examples, because abstract diagrams convince no one who has shipped to production.

§ 01What we have actually been doing wrong

Begin with a specific failure. A user opens a trading app and submits an order to buy one hundred shares of MSFT. The mobile client sends the request, with a bearer access token in the Authorization header, to api.broker.example. The gateway accepts the token, verifies its signature against the IdP's JWKS, and forwards the request inward.

What does "forwards inward" mean? In most architectures I have audited, it means one of two things. Either the gateway strips the user's access token and re-authenticates as itself to internal services — losing the user identity entirely, which downstream services then have to reconstruct from headers, which is a category of bug that has shipped to production at every major company on earth. Or the gateway forwards the user's access token unchanged, deeper and deeper into the mesh, where it sits in request logs and in-flight buffers and tracing spans waiting to be exfiltrated.

Either way, four specific things become possible:

The interior is not friendly. It is merely less hostile, which is a different thing entirely, and the difference is precisely where every breach lives. — On the perimeter fallacy

The right framing is this: the access token is a credential for crossing the edge. Inside, we need a different artifact — one that binds, in a way the cryptography enforces, the user's identity to this specific transaction with these specific parameters, valid for the brief window required to fan out and return. The Transaction Token is that artifact.

§ 02What a Txn-Token actually is

Strip away the spec language and a Txn-Token is a short-lived signed token — typically a JWT, though the draft permits other structured formats like CWT — that is minted at the edge of a trust domain in exchange for, or in response to, whatever credential or context arrived from outside. (Token exchange is the common path I'll use throughout, but the draft also allows direct issuance, workload-initiated issuance, and internally-initiated transactions with no external token at all.) It carries three things that nothing else in your stack carries simultaneously:

  1. The user identity — who, on whose behalf, made this happen — bound into a subject claim (commonly sub) signed by an issuer that downstream services trust.
  2. The workload identity — which workload requested this Txn-Token from the TTS — bound into the req_wl claim. The draft treats this as a first-class pillar, not a footnote: who minted matters as much as who initiated.
  3. The transaction context — what action, on what object, under what intent — bound into the tctx claim (§10.2 of the draft), which downstream services treat as the canonical authorization input.
  4. The requester context — IP, authentication method, device, anything that informs risk but doesn't drive authorization — bound into the rctx claim (also §10.2).

Every downstream workload validates the signature, the audience, the expiry — and then reads its authorization inputs from the signed claims, not from the request body or the headers. The request body becomes, in a sense, advisory: the truth lives in the token.

01 · Anatomy of the call chain
The basic Txn-Token flow: edge exchange and inward propagation TRUST DOMAIN — trust-domain.example External client access token API gateway external endpoint Txn-Token Service mints & signs RFC 8693 exchange Order service internal workload Execution internal workload Ledger internal workload 1. access token 2. exchange 3. Txn-Token 4. Txn-Token forwarded 5. same token, unmodified

Within a single transaction, the Txn-Token flows forward unchanged. A new transaction — initiated, not just continued — gets a fresh mint.

§ 03The stock trade, in mechanism

Concrete is better than abstract. Let me walk through the trade end-to-end, with the actual wire format, because the wire format is where the design either earns its keep or quietly fails.

Step one — the edge exchange

The user submits the trade. The API gateway receives the request bearing the user's OAuth access token. Before it dispatches anything inward, it makes one call: an RFC 8693 token exchange to the Transaction Token Service, asking to swap the access token for a Txn-Token scoped to this specific trade.

POST /txn-token-service/token_endpoint HTTP/1.1
Host: txn-token-service.trust-domain.example
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&requested_token_type=urn:ietf:params:oauth:token-type:txn_token
&audience=trust-domain.example
&scope=trade.stocks
&subject_token=eyJhbGciOiJFUzI1NiIsImtpZC...   // the user's access token
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&request_context={"req_ip":"69.151.72.123","authn":"urn:ietf:rfc:6749"}
&request_details={"action":"BUY","ticker":"MSFT","quantity":"100"}
    

Three things to notice. First, the gateway uses RFC 8693 token exchange — this is the path I'll show throughout, though as flagged at the top, the draft permits other minting mechanisms. Second, I'm using an OAuth-style scope string (trade.stocks) for familiarity. The draft does not actually define OAuth-style scopes as a normative part of the Txn-Token; it leaves the authorization-intent encoding to the profile. You could equally express this as a transaction-claim field with no scope string at all. Third, the gateway passes the desired transaction parameters in request_details — but it cannot dictate what ends up in the resulting token. That decision belongs to the TTS.

Step two — the mint

The TTS validates the access token, looks up the user, evaluates whatever policy is appropriate (does this user have trading entitlements? is the account in good standing? is the customer geo-permitted for this instrument?) — and then the TTS itself mints and signs the Txn-Token. No other entity in the architecture is permitted to sign one. The TTS publishes its JWKS at a well-known endpoint; every downstream workload verifies the JWS signature against that key. Here is the body the TTS produced and signed:

{
  "iat": 1686536226,
  "aud": "trust-domain.example",
  "exp": 1686536586,           // 6 minutes
  "txn": "97053963-771d-49cc-a4e3-20aad399c312",
  "sub": "d084sdrt234fsaw34tr23t",
  "req_wl": "apigateway.trust-domain.example",
  "scope": "trade.stocks",
  "rctx": {
    "req_ip": "69.151.72.123",
    "authn": "urn:ietf:rfc:6749"
  },
  "tctx": {
    "action": "BUY",
    "ticker": "MSFT",
    "quantity": "100",
    "customer_type": { "geo": "US", "level": "VIP" }
  }
}

What goes back on the wire to the gateway is the standard token-exchange response with the signed JWT in the access_token field:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "token_type": "N_A",
  "issued_token_type": "urn:ietf:params:oauth:token-type:txn_token",
  "access_token": "eyJ0eXAiOiJ0eG50b2tlbitqd3QiLCJhbGciOiJSUzI1NiIsImtpZCI6InR0cy0yMDI2LXEyIn0..."
}

The header is {"typ":"txntoken+jwt","alg":"RS256","kid":"tts-2026-q2"} — the kid identifies which of the TTS's signing keys was used, so downstream services can pick the right public key from the JWKS during rotation. The TTS signs the JWS with the corresponding private key, base64url-encodes the result, and returns it inside the access_token field of the RFC 8693 token-exchange response (the field is named access_token for protocol-compatibility reasons; the issued_token_type on the response is urn:ietf:params:oauth:token-type:txn_token, which is how the gateway knows it received a Txn-Token).

Notice that customer_type was not in the gateway's request — the TTS computed it from policy. This is the discipline at the core of the design: the requester suggests, the TTS decides. If a compromised gateway could stuff arbitrary fields into tctx, the signature would be theatrical. So tctx is the TTS's signed verdict, not the requester's wish-list.

Step three — propagation

The gateway now calls the order service. The Txn-Token rides in a dedicated header — not the Authorization header, which is reserved in this architecture for service-to-service authentication (mTLS, SPIFFE SVIDs, what have you):

POST /orders HTTP/1.1
Host: order-service.trust-domain.example
Txn-Token: eyJ0eXAiOiJ0eG50b2tlbitqd3QiLC...
Content-Type: application/json

{"action": "BUY", "ticker": "MSFT", "quantity": 100}

The order service validates the JWS signature against the TTS's published key. It verifies aud matches its trust domain. It verifies exp is in the future. And then — this is the part most teams underweight when they first read the spec — it reads the trade parameters from tctx, not from the request body. The request body is convenient; the token is canonical. If a compromised midstream service rewrites the body to buy ten thousand shares, the order service still reads "100" from tctx.quantity and the attack fails silently.

When the order service calls execution as part of the same logical transaction, it forwards the same Txn-Token, unmodified. When execution calls the ledger to record the same trade, same thing. The token is the spine of one transaction; every workload independently verifies it; nobody mutates it. If, however, a workload decides to initiate a new transaction inside the trust domain — say the order service spawns an asynchronous fraud-review workflow — that new transaction warrants its own Txn-Token, minted by the TTS, scoped to its own context. The spec is careful here: the rule is "don't mutate a Txn-Token mid-call-chain," not "one token per request that ever enters the trust domain."

§ 04How the four failures die

Walk back to the four threats. Each one dies for a specific, attributable reason:

Spurious invocation
No valid Txn-Token means no valid transaction. A compromised workload that wants to fabricate a call has no way to forge a token that downstream services will accept — the TTS is the only signer, and the TTS demands evidence of a real originating request.
User impersonation
The sub claim is signed by the TTS, which derived it from a validated subject token, which was tied to a real authentication event. A midstream service cannot rewrite sub without breaking the signature, and it cannot resign the token without the TTS's key.
Parameter tampering
The transaction parameters live in tctx, signed. Workloads read authorization inputs from the token, not from the request body. A compromised hop can rewrite the body all it wants — the execution service is reading from a different source of truth.
Token theft & replay
The access token never enters the Txn-Token. The Txn-Token itself is audience-bound to one trust domain, scoped narrowly, lives for minutes, and carries a unique txn identifier that workloads can cache to enforce single-use. Steal it and you get one trivial action in a six-minute window.

The token does not authorize a session. It authorizes a transaction. The shift in granularity is the entire architectural point. — On token semantics

§ 05Healthcare, where the design earns its keep

The stock trade is the textbook example. The example I find more revealing — the one that shows you what tctx can really do in the hands of a thoughtful TTS — is healthcare.

Consider Dr. Sarah Chen, an attending physician at a teaching hospital. She logs into the clinical workstation in the ICU, authenticates with WebAuthn plus TOTP, opens the patient on her 2pm appointment, and clicks "show recent labs." That single click fans out, in any modern EHR architecture, to a dozen internal services: patient demographics, the laboratory information system, radiology PACS, pharmacy, an audit logger that exists because HIPAA requires it to exist.

Now imagine the TTS mints and signs this Txn-Token (header {"typ":"txntoken+jwt","alg":"RS256","kid":"hospital-tts-2026-q2"}):

{
  "iat": 1747263600,
  "aud": "hospital.example",
  "exp": 1747263780,           // 3 minutes
  "txn": "rx-2026-05-14-a8f3e9c4-7d21-4b8a-9e5f-c3d8a1b2e4f7",
  "sub": "user:schen@hospital.example",
  "scope": "patient.records.read",
  "rctx": {
    "req_ip": "10.42.7.18",
    "authn": "webauthn+totp",
    "device_id": "WS-ICU-04"
  },
  "tctx": {
    "patient_id": "MRN-8821334",
    "purpose_of_use": "treatment",
    "encounter_id": "ENC-2026-05-14-2241",
    "requester_role": "attending_physician",
    "relationship": "treating",
    "data_classes_permitted": ["demographics", "labs", "imaging_reports"]
  }
}

Look closely at data_classes_permitted. Dr. Chen did not ask for "demographics, labs, imaging reports." She clicked "show recent labs." The TTS — knowing her role, her relationship to this patient, her authentication strength, the encounter context — produced an explicit allowlist of data classes she is permitted to read for this specific encounter. Mental health notes are not in the list. Genetic counseling records are not in the list. If the lab service is compromised and tries to query the mental health system using this token, the mental health system reads tctx.data_classes_permitted, fails to find its data class, and refuses — regardless of who is asking.

The architectural inversion

Notice what just happened. The authorization decision was made once, at the edge, by the TTS — which has access to the full policy engine, the role hierarchy, the relationship graph, the consent registry. Every downstream service now enforces that decision uniformly, by reading a signed field. We have inverted the usual pattern, where every microservice has to re-derive the authorization decision from raw inputs and inevitably gets it slightly wrong in slightly different ways.

Break-glass, observably

The pattern composes elegantly with edge cases. Consider the ER physician who needs to read records for an unconscious patient with whom she has no prior treatment relationship. The traditional answer is "break-glass" — emergency override — which in most EHRs amounts to an unaudited or weakly-audited bypass that gets used and abused with alarming frequency.

With Txn-Tokens, break-glass is a first-class TTS request: request_details.purpose_of_use=emergency. The TTS issues a token with tctx.purpose_of_use=emergency and tctx.break_glass=true. Every downstream service still authorizes the access, but now also logs it at a higher audit tier, triggers a real-time alert to the compliance team, and records the entire fan-out — every service touched — under one txn identifier. Forensics goes from "find every log line that mentions MRN-8821334 around 2pm Tuesday across forty services" to "show me the call chain for txn rx-2026-05-14-a8f3e9c4-...."

That second query is deterministic. The first is best-effort archaeology. The difference matters enormously the day the regulator calls.

§ 06The two claims, and the discipline they require

The spec is deliberately thin on what goes inside tctx and rctx. Section 10.2.2 suggests two fields for rctx (req_ip, authn) and explicitly says you can include "any values the TTS determines are relevant." Section 10.2.3 defines zero required fields for tctx and says its content is "determined by the TTS." This is a framing contract, not a schema.

The freedom is real, but it requires discipline. Three rules I have arrived at, after a lot of whiteboard time:

Rule one — authorization gates go in tctx

If a downstream service makes an authorization decision based on a field, it belongs in tctx. The data_classes_permitted allowlist, the patient ID, the trade ticker, the customer tier when tiering affects what's permitted — all tctx. The IP address, the authentication method, the user agent, the device — all rctx, because they inform risk telemetry but should not drive yes/no decisions deep in the call chain.

Rule two — the TTS decides, the requester suggests

Never let a field in tctx be a verbatim copy of something the requester asked for, with no validation. The whole point of the signature is that tctx represents the TTS's policy verdict. If a compromised gateway can set tctx.bypass_audit=true just by including it in request_details, your design is broken. The TTS must own the schema and the validation.

Rule three — tctx is not a session

The temptation, once you have a signed envelope, is to stuff everything into it. The user's full role list, every permission flag, the entire customer record. Resist this. A token with forty-seven fields is a juicy target and a coupling nightmare; it drifts back toward the "session token" model that Section 14.11 of the spec explicitly disclaims. The Txn-Token is a per-transaction policy decision, frozen at the edge, narrow enough that one stolen token equals one trivial action. The narrower the better.

The narrower you can make the token, the less it matters when one of them leaks. Design for the leak, not for the happy path. — On blast radius

§ 07Where this fits with everything else

Txn-Tokens do not replace your existing identity primitives. They sit alongside them, and the layering matters.

Workloads still need to authenticate to each other — that is what mTLS and SPIFFE SVIDs are for. A SPIFFE ID answers "is this caller really the order service?" It tells you nothing about whether the call should be permitted. The Txn-Token answers "given that this is the order service, is this specific call part of a transaction that the user actually authorized?" One is workload identity; the other is transaction context. You need both. Section 14.11 of the spec is explicit on this: Transaction Tokens are not authentication credentials. Treating them as such is the most common architectural mistake I expect to see during early adoption.

Access tokens still exist, and still handle the edge. The Txn-Token is what the access token becomes after it has authenticated the user at the boundary and the system has decided to admit the request. Think of the access token as the visa, the Txn-Token as the boarding pass for one specific flight.

And the broader trust-domain architecture from the WIMSE working group is the context this all lives in: workloads with SPIFFE identities, communicating over mTLS or WIMSE Workload Proof Tokens, inside an explicitly bounded trust domain — and within that domain, transactions are carried by Txn-Tokens. Each layer answers a different question, and the design is strongest when the layers stay in their lanes.

§ 08What I think will actually happen

The honest assessment: this is a draft, it is on the standards track, and it will probably ship as an RFC sometime in the next twelve to eighteen months. The early adopters will be the people who already feel the pain — financial services, healthcare, anyone with regulatory audit requirements that make "best-effort log correlation" an inadequate answer when the regulator calls.

The hard parts of adoption will not be the protocol. RFC 8693 token exchange already has implementations; the Txn-Token profile is a small delta. The hard parts will be:

None of these are blocking. All of them are work.

· · ·