OAuth·for·Agents

Scenario 05 · adds a trust boundary

The work crosses a company boundary

An agent inside your CRM vendor's product needs a contract in your company's document storage. Your identity provider knows the user. The storage API has never heard of the vendor and has no reason to trust it. 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: a deliberate integration built once becomes an agent deciding mid-task that it needs a file nobody planned for it to touch. The old answer, pasting an API key from system B into system A, scales to zero and is invisible to the people responsible for the data.

Two shortcuts everyone tries first, both wrong in instructive ways:

  • Have the IdP issue tokens for everything. The vendor exchanges the user's ID token for an access token the storage API accepts directly. That API now trusts a foreign issuer, then another, each with its own claim conventions and revocation model. Every resource server becomes an integration surface for every IdP your customers use.
  • Just send the ID token. It's a standard format, so it feels safe. It isn't: that token was minted for a different audience, and accepting it breaks audience binding.
The tell

Both shortcuts share a symptom: a resource server accepting a credential created for somebody else. Any time your design has that property, the audience check has quietly become optional, and it's the only thing stopping a token minted for one app from being replayed at another.

The mechanism

Two exchanges, and every domain keeps its own tokens

OAuth Identity and Authorization Chaining Across Domains, in the RFC Editor queue as of August 2026, composes two things that already existed: Token Exchange and JWT authorization grants. The enterprise profile, the Identity Assertion JWT Authorization Grant (ID-JAG, or 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 short-lived, audience-bound grant naming the target resource. Not an access token, a grant: good for exactly one thing, asking a specific authorization server for a token.
  • Hop two, at the target's authorization server. That server trusts your IdP's signing keys, validates the grant, and issues its own access token with its own lifetime and revocation.

Everything good follows from that split. The storage API validates tokens from the one issuer it always did, and the IdP stays the policy point, deciding whether this vendor may request that resource for this user. The user's identity survives, and so does the fact that an agent is acting.

The flow

Agent in the CRM, contract in the vault

Identity chaining with an ID-JAG
  1. Agent (in CRM)→Enterprise IdP

    Presents the user's ID token and asks to exchange it for a grant targeting the storage API, authenticating as itself while doing so. Everything from scenario 00 still applies.

  2. Enterprise IdP→Agent (in CRM)

    Applies admin policy: may this application request 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.

  3. Agent (in CRM)→Storage AS

    Presents the ID-JAG as a JWT bearer authorization grant.

  4. 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.

  5. 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

This rung matters because it returns something agents took away: the ability for an organization to see and control what its software does 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. act travels through the chain, so the vault's log can say the deal agent did this for that person, rather than "the CRM did this."
Composition, not invention

Identity chaining is token exchange, which shipped in 2020, feeding a JWT bearer grant from 2015. The new work is a profile, not a protocol: which token types, which claims, which validation rules. That's the pattern for most of this site, and why the pieces fit together.