Scenario 05 · adds a trust boundary
The work crosses a company boundary
An agent inside your CRM vendor's product needs to read a contract in your company's document storage. Your identity provider knows the user. The storage API has never heard of your CRM vendor and has no reason to trust anything it says. Multiply by the forty SaaS products your company bought.
Why this got urgent
Cross-domain access isn't new. What agents changed is the volume and the initiative: it used to be a deliberate integration a team built once, and now it's an agent deciding mid-task that it needs a file from a system nobody planned for it to touch. The old answer — a user generates an API key in system B and pastes it into system A — scales to zero and is invisible to the people responsible for the data.
There are two shortcuts everyone tries first. Both are wrong in instructive ways.
Shortcut one: have the IdP issue tokens for everything
The vendor exchanges the user's ID token at your IdP for an access token that the storage API accepts directly. One stop, clean-looking. It fails because the storage API now has to trust a foreign token issuer — and then another, and another, each with its own claim conventions, key rotation schedule, expiry semantics and revocation model. You've made every resource server in your company into an integration surface for every IdP its customers use.
Shortcut two: just send the ID token
Skip the exchange and present the original ID token to the storage provider's authorization server. It's a standard format, so it feels safe. It isn't: that token was minted for a different audience entirely. Accepting it breaks audience binding, which is the one property preventing a token issued for app A from being replayed at app B by anyone who gets hold of it.
Both shortcuts share a symptom: a resource server ends up accepting a credential that was created for somebody else. Any time your design has that property, the audience check has quietly become optional, and the audience check is load-bearing.
The mechanism
Two exchanges, and every domain keeps its own tokens
OAuth Identity and Authorization Chaining Across Domains — approved and in the RFC Editor queue as of August 2026 — composes two things that already existed: Token Exchange and JWT authorization grants. The profile for the enterprise-app case, the Identity Assertion JWT Authorization Grant (ID-JAG, also known as Cross-App Access), fills in the details.
The shape is two hops:
- Hop one, at the IdP. The vendor exchanges the user's identity for a purpose-built, short-lived, audience-bound grant naming the target resource. Not an access token — a grant. It is good for exactly one thing: asking a specific authorization server for a token.
- Hop two, at the target's authorization server. That server, which has a federation relationship with your IdP and trusts its signing keys, validates the grant and issues its own access token in its own format with its own lifetime and revocation.
Everything good follows from that split. The storage API validates tokens from one issuer, the one it always did. The IdP stays the policy point — it decides whether this vendor may request access to that resource for this user, and it can stop doing so instantly. The user's identity survives the crossing, and so does the fact that an agent, not the user, is doing the acting.
The flow
Agent in the CRM, contract in the vault
-
Agent (in CRM)→Enterprise IdP
Presents the user's ID token and asks to exchange it for a grant targeting the storage API. The CRM authenticates as itself while doing this — everything from scenario 00 still applies.
-
Enterprise IdP→Agent (in CRM)
Applies admin policy: may this application request access to that resource on this user's behalf? If so, issues an ID-JAG — a JWT audienced to the storage provider's authorization server, valid for seconds, naming the user as subject and the agent as actor.
-
Agent (in CRM)→Storage AS
Presents the ID-JAG as a JWT bearer authorization grant.
-
Storage AS→Agent (in CRM)
Validates the signature against the federated IdP's keys, checks the audience is itself, applies its own policy, and issues a normal, local, DPoP-bound access token.
-
Agent (in CRM)→Storage API
Reads the contract. The API validated a token from its own authorization server, as it always has.
// hop one — at the enterprise IdP
POST /oauth2/token HTTP/1.1
Host: idp.customer-co.com
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange
&requested_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid-jag
&subject_token_type=urn%3Aietf%3Aparams%3Aoauth%3Atoken-type%3Aid_token
&subject_token=eyJhbGciOiJSUzI1NiIsImtpZCI6ImlkcC0yMDI2In0...
&resource=https%3A%2F%2Fapi.vault.example%2F
&scope=documents.read
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
client-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJFUzI1NiJ9...
// the ID-JAG that comes back — a grant, not an access token
{
"iss": "https://idp.customer-co.com",
"sub": "user:9f3c21", // the person
"aud": "https://auth.vault.example", // exactly one authorization server
"client_id": "https://crm.vendor.example/clients/deal-agent",
"scope": "documents.read",
"act": { "sub": "agent:deal-agent/inst-7741" }, // the agent, as actor
"jti": "jag_01KA7F2M",
"exp": 1787894580 // seconds, not hours
}
// hop two — at the storage provider's own authorization server
POST /oauth2/token HTTP/1.1
Host: auth.vault.example
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6ImlkcC0yMDI2In0...
→ 200 OK
{
"access_token": "...", // vault.example's own token, vault.example's own rules
"token_type": "DPoP",
"expires_in": 300,
"scope": "documents.read"
}
What the enterprise gets back
The reason this rung matters isn't elegance. It's that it returns something that agents took away: the ability for an organization to see and control what its software is doing with its data.
- One place to grant. An admin allows the CRM to request document access, once, rather than every user pasting a key.
- One place to revoke. Turn off the vendor at the IdP and every downstream token stops being renewable, across every resource, immediately.
- One place to look. The IdP's log has every cross-domain request: which app, which user, which resource, which agent instance.
- No new trust for the resource. The storage API's threat model didn't change. It validates its own tokens.
- The actor survives.
acttravels through the chain, so the vault's log can say the deal agent did this for that person — not "the CRM did this."
Look at what identity chaining actually is: token exchange, which shipped in 2020, feeding a JWT bearer grant, which shipped in 2015. The new work is a profile — which token types, which claims, which validation rules — not a new protocol. That's the pattern for most of this site, and it's why the pieces fit together at all.