Enterprises sign every application into one identity provider with OpenID Connect or SAML. But when one application needs to call another application's API on the user's behalf, OAuth's answer has been a direct app-to-app flow (an authorization code handshake between the two apps) that the IdP never sees (Section 1). The IdP that manages who can reach what sits the delegation out.
Cross-App Access (XAA) moves that decision inside the trust boundary that already exists: the IdP both apps already trust for SSO brokers the delegation. It does so by profiling identity chaining into concrete deliverables (Section 1): the Token Exchange request that obtains an ID-JAG, the ID-JAG itself with its claims, the processing rules the Resource Authorization Server applies when exchanging an ID-JAG for an access token, and the metadata used to advertise support.
The draft's own example: a wiki app wants to read your chat history at a chat app from another vendor. Both are SSO'd into the enterprise IdP. With XAA, the wiki asks the IdP for a grant naming the chat app's authorization server, and trades that grant for an access token (Section 4.1).
Four roles (Section 2.1): the Client (the wiki app), the IdP Authorization Server (the enterprise IdP), the Resource Authorization Server (the chat app's AS), and the Resource Server (the chat API). Three separate registration relationships connect them (Section 5): client-to-IdP for SSO, client-to-Resource-AS for API access, and Resource-AS-to-IdP - the pre-existing SSO trust the whole pattern rides on.
The Client and the IdP AS live in trust domain A; the Resource AS and Resource Server in trust domain B. The ID-JAG is the object that crosses the boundary: signed by the IdP, consumed by the Resource AS. The Resource Server never talks to the IdP at all - it validates access tokens from its own AS only (Section 2.1).
The two hops are steps 2 and 3: the grant crosses the trust boundary; the access token never leaves domain B. (Sections 2.1, 4.1, 5)
The client authenticates to the IdP - always; a request without client authentication is rejected withinvalid_client (Section 4.3.3) - and presents the Identity Assertion it got from SSO as thesubject_token. The audience parameter is the Resource Authorization Server's issuer identifier, exactly one value; a multi-value request is refused with invalid_request, and an audience the IdP has no trust relationship with gets invalid_target (Sections 4.3, 4.3.3). The IdP validates that the subject token really is what it claims to be - a token the IdP itself issued for SSO, not an access token or a grant being recycled (Section 4.3.3) - applies its policy, and returns the signed ID-JAG (Section 4.3.4).
POST /token HTTP/1.1 Host: idp.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:id-jag &audience=https://chat.example/ &resource=https://api.chat.example/ &scope=chat.read &subject_token=eyJhbGciOiJSUzI1NiIs... (the ID Token) &subject_token_type=urn:ietf:params:oauth:token-type:id_token &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer &client_assertion=eyJhbGciOiJSUzI1NiIs... (client authentication)
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"issued_token_type": "urn:ietf:params:oauth:token-type:id-jag",
"access_token": "eyJhbGciOiJSUzI1NiIs... (the ID-JAG itself)",
"token_type": "N_A",
"expires_in": 300
}The ID-JAG rides in access_token because Token Exchange requires the parameter; it is a grant, not an access token - hence token_type=N_A and a distinct issued_token_type (Section 4.3.4).
The ID-JAG is a JWT whose header typ MUST be oauth-id-jag+jwt (Section 3.1). RS256 is mandatory to implement for both authorization servers, and Resource Authorization Servers SHOULD also support ES256 (Section 3, RFC 7523 Section 5). Seven claims are REQUIRED (Section 3.1): iss and aud name the two ends of this specific trust relationship, sub and optionally sub_id identify the user,client_id names the client at the Resource AS that will act for the user, andjti, exp, iat make the grant trackable and perishable.
{
"typ": "oauth-id-jag+jwt", (header)
"iss": "https://idp.example/", REQUIRED the IdP AS
"sub": "U019488227", REQUIRED the user, in the IdP's namespace
"aud": "https://chat.example/", REQUIRED the Resource AS issuer
"client_id": "f53f191f9311af35", REQUIRED the client at the Resource AS
"jti": "9e43f81b64a33f20116179", REQUIRED unique token id
"exp": 1311281270, REQUIRED expiry (NumericDate)
"iat": 1311280970, REQUIRED issued at
"resource": "...", OPTIONAL RFC 8707 resource identifiers
"scope": "...", OPTIONAL space-separated scopes
"email": "...", OPTIONAL RECOMMENDED with aud_sub for subject resolution
"aud_sub": "...", OPTIONAL the Resource AS's own id for the user
"sub_id": { ... } OPTIONAL SAML NameID subject identifier (Section 3.2)
}Full claims table with every OPTIONAL member: the draft primer. Rule-by-rule validation of each claim: the rules explorer.
Two claims deserve the emphasis they get in the draft. aud MUST carry exactly the receiving Resource AS's issuer - a string or a single-element array, enforced to prevent audience injection (Section 4.4.1). And subject resolution is the point of the whole document: it is RECOMMENDED that the grant carry email and/or aud_sub so the Resource AS can resolve the user to a local account, including just-in-time provisioning when the user has never signed in there before (Section 3.1).
The client redeems the ID-JAG at the Resource AS's token endpoint as an ordinary RFC 7523 assertion (Section 4.4). The Resource AS SHOULD require client authentication here too; if the client does not authenticate, the request MUST carry a sender-constrained grant instead (Section 4.4). Then the processing rules run: verify typ, verify the signature against the trusted issuer's keys from itsjwks_uri, check aud, check client_id against the authenticated client, check exp - each failure maps to invalid_grant, while a valid grant for an unentitled user maps to access_denied (Sections 4.4.1, 4.4.3).
POST /token HTTP/1.1 Host: chat.example Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer &assertion=eyJhbGciOiJSUzI1NiIs... (the ID-JAG)
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
{
"token_type": "Bearer",
"access_token": "2YotnFZFEjr1zCsicMWpAA", (an ordinary access token)
"expires_in": 86400,
"scope": "chat.read"
}No refresh token SHOULD come back: the ID-JAG replaces it, and the client MAY re-submit the same grant until it expires (Section 4.4.4).
The obvious alternative - the IdP issuing resource access tokens directly - is exactly what the draft does not do. Unlike profiles where the IdP issues the access tokens the Resource Server consumes, XAA preserves the Resource Authorization Server as the issuer of access tokens for its own resources (Section 1). The IdP centralizes identity, SSO trust, and the decision to broker access; the Resource AS keeps independent control of its tokens, its scopes, and its local policy (Section 1). The alternative couples every API to a growing list of foreign token issuers, and the draft pins the restraint down: an IdP MUST NOT honor its own ID-JAG by issuing access tokens unless explicitly configured, and then no more generously than a direct issuance would have been (Section 9.4).
The cost of the shape is the mapping work in Section 5: the IdP must know which client_id at the Resource AS corresponds to this client, and which sub value the Resource AS's own SSO registration would carry - both established out of band. Client ID Metadata Documents are called out as a namespace that removes the client mapping (Section 5.1). Metadata advertisement closes the loop: an IdP SHOULD list the id-jag token type in identity_chaining_requested_token_types_supported, a Resource AS SHOULD list the grant profile in authorization_grant_profiles_supported, and that advertisement MUST be coupled to grant_types_supported carryingurn:ietf:params:oauth:grant-type:jwt-bearer (Sections 7.1, 7.2).
| If this is tried | The draft's answer | Cite |
|---|---|---|
| Grant minted for a different resource server | aud MUST carry exactly the receiving Resource Authorization Server’s issuer identifier, as a string or a single-element array; anything else is rejected with invalid_grant | Section 4.4.1 |
| Forged grant | the signature MUST validate against the trusted issuer’s keys, obtained from the issuer’s jwks_uri; RS256 is mandatory to implement | Sections 4.4.1 and 3 |
| Wrong token type smuggled in as the grant | the JWT typ header MUST be oauth-id-jag+jwt - explicit typing, as recommended by RFC 8725 Section 3.11 | Sections 3.1 and 4.4.1 |
| Cross-service identity confusion via SAML NameIDs | a pairwise nameid MUST be the value the IdP would issue to the Service Provider matching the target RAS; the RAS MUST NOT resolve on the nameid alone without the qualifying scope members, and transient NameID formats MUST NOT be used | Section 3.2.2 |
| Stolen subject token (ID Token, SAML assertion, or refresh token) | client authentication at the IdP is the remaining barrier; stored subject tokens SHOULD sit in a separate protection domain from client credentials, out of logs, dumps, and URIs | Sections 9.2 and 4.3 |
| Unauthenticated presenter at the Resource AS | the Resource AS MUST reject a request that has neither client authentication nor a sender-constrained ID-JAG; key binding comes via DPoP or mTLS through the cnf claim | Sections 4.4 and 9.11 |
| Replay of a captured grant | the Resource AS MAY enforce single use by tracking jti, MAY cap exp by policy, and SHOULD NOT return a refresh token - the ID-JAG itself is the re-submittable grant | Section 4.4.4 |
| Trust-domain broadening by the IdP | an IdP MUST NOT issue access tokens in response to an ID-JAG it issued itself unless explicitly configured, and then no more generously than direct issuance would allow | Section 9.4 |
And the map has honest blanks the draft says out loud: replay tracking is a MAY, so two compliant Resource ASs can differ (Section 4.4.4); subject-uniqueness rules need Resource-AS-side state a token inspector does not have (Section 3.1); and policy - which user may reach which app - is the IdP's administrator-defined judgment, not a protocol outcome (Section 4.3.3). Where the line sits between "the protocol guarantees" and "the deployment decides" is the difference between validating a grant and trusting a system.
Normative - the documents this page's cites resolve to:
- draft-ietf-oauth-identity-assertion-authz-grant-05 (work in progress) - the primary source for every section cite above
- draft-ietf-oauth-identity-chaining-17 (work in progress)
- RFC 6749 - The OAuth 2.0 Authorization Framework
- RFC 7521 - Assertion Framework for OAuth 2.0
- RFC 7523 - JWT Profile for OAuth 2.0 Authorization Grants
- RFC 8693 - OAuth 2.0 Token Exchange
- RFC 8707 - Resource Indicators for OAuth 2.0
- RFC 8725 - JSON Web Token Best Current Practices
- RFC 9396 - OAuth 2.0 Rich Authorization Requests
- RFC 9449 - OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- RFC 8705 - OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Tokens
- RFC 9493 - Subject Identifiers for Security Event Tokens
- OpenID Connect Core 1.0
- SAML 2.0 Core (OASIS)
Informative - status and context:
- Datatracker: draft-ietf-oauth-identity-assertion-authz-grant
- RFC 9068 - JWT Profile for OAuth 2.0 Access Tokens (the profile XAA contrasts itself with)
- The draft primer on this site - status, history, claims table, keyword map