# Cross App Access (XAA)

:::note{title="Beta"}

Cross App Access support is in beta. The configuration model and policy options
may change before general availability.

:::

Cross App Access (XAA) lets one application reach another application's API on a
user's behalf **through the identity provider both apps already trust** —
instead of running a separate, point-to-point OAuth connection between them. For
AI agents and MCP, that means an agent can call a protected upstream MCP server
for a user without the user re-consenting to every app pair, and without
credentials sprawling outside the identity provider's view.

The Zuplo MCP Gateway sits in the middle of this flow and runs the XAA token
exchange for you. An MCP client connects to the gateway with ordinary MCP OAuth;
the gateway, acting as the XAA _requesting app_, mints the cross-app grant from
your identity provider and redeems it at the upstream's authorization server.
Neither the MCP client nor the upstream MCP server has to implement XAA itself.

## The problem XAA solves

Single sign-on (SSO) solves _login_: a user authenticates once with an identity
provider (Okta, Microsoft Entra, Auth0) and gets into every connected app. SSO
does **not** solve _app-to-app API access_. When one app needs to call another
app's API on the user's behalf, the two apps today run a direct OAuth flow
between themselves. The
[IETF draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/)
describes this as a connection that "bypasses the trusted identity provider" and
is "invisible to the identity provider managing the ecosystem."

AI agents make this gap urgent. An agent in one app increasingly needs to reach
into other SaaS apps' data on the user's behalf. The naive approach produces:

- **Consent-screen sprawl** — the user re-authorizes every agent against every
  downstream app individually.
- **Credential sprawl** — long-lived tokens scattered per app pair, invisible to
  IT.
- **No central governance** — security teams can't see or control what connects
  to what, and offboarding means hunting access down service by service.

XAA routes app-to-app access back through the identity provider both apps
already trust. The IdP becomes the single place where IT decides which app can
talk to which app, enforces conditional-access policy, and audits every
delegation — while the upstream app's authorization server stays the
access-token issuer. The trust boundary isn't broken; it's brokered.

## How the flow works

XAA is defined by the **Identity Assertion JWT Authorization Grant (ID-JAG)** —
`draft-ietf-oauth-identity-assertion-authz-grant`. It chains two OAuth exchanges
across two authorization servers:

1. **Identity assertion → ID-JAG**, at the identity provider, using
   [RFC 8693 token exchange](https://www.rfc-editor.org/rfc/rfc8693). The
   requesting app presents the user's ID token (the identity assertion) and asks
   for a grant scoped to one specific target authorization server. The IdP
   evaluates policy and returns a short-lived, audience-restricted **ID-JAG** —
   a signed JWT with the header `typ: oauth-id-jag+jwt`.
2. **ID-JAG → access token**, at the upstream's resource authorization server,
   using the
   [RFC 7523 JWT bearer grant](https://www.rfc-editor.org/rfc/rfc7523). The
   requesting app presents the ID-JAG as an `assertion` and receives a normal
   access token for the upstream API.

The ID-JAG is a _grant_, not an access token — it can't call an API directly. It
exists only to be redeemed in the second exchange.

When the Zuplo gateway fronts an XAA-protected upstream, the gateway plays the
**requesting app** for both exchanges:

<Diagram height="h-80">
  <DiagramNode id="client">MCP Client</DiagramNode>
  <DiagramGroup id="gateway" label="Zuplo MCP Gateway (requesting app)">
    <DiagramNode id="inbound" variant="zuplo">
      OAuth 2.1 server
    </DiagramNode>
    <DiagramNode id="requestor" variant="zuplo">
      XAA requestor
    </DiagramNode>
  </DiagramGroup>
  <DiagramNode id="idp">Identity Provider</DiagramNode>
  <DiagramGroup id="upstream" label="Upstream app">
    <DiagramNode id="ras">Resource AS</DiagramNode>
    <DiagramNode id="mcp">MCP server</DiagramNode>
  </DiagramGroup>
  <DiagramEdge from="client" to="inbound" label="MCP OAuth (no XAA)" />
  <DiagramEdge from="requestor" to="idp" label="1. ID token → ID-JAG" />
  <DiagramEdge from="requestor" to="ras" label="2. ID-JAG → access token" />
  <DiagramEdge from="requestor" to="mcp" label="3. Bearer token" />
</Diagram>

Step by step, for a tool call to an XAA-protected upstream route:

1. The MCP client connects to the gateway route with **ordinary MCP OAuth** —
   discovery, PKCE, and a bearer token issued by the gateway. This leg is _not_
   XAA. During the gateway's browser-login step, the user authenticates with the
   identity provider, and the gateway captures the user's IdP identity
   assertion.
2. On a tool call, the gateway exchanges the user's ID token at the IdP for an
   **ID-JAG** audience-restricted to the upstream's resource authorization
   server (RFC 8693 token exchange).
3. The gateway redeems the ID-JAG at the upstream resource authorization server
   for an upstream **access token** (RFC 7523 JWT bearer grant).
4. The gateway forwards the tool call to the upstream MCP server with
   `Authorization: Bearer <upstream access token>` and caches the upstream token
   per user for subsequent calls.

:::note{title="In requesting-app mode, the client → gateway leg is plain OAuth"}

This is the most common point of confusion. In the flow above, the MCP client
never speaks XAA — it runs the standard MCP authorization flow against the
gateway, and all ID-JAG exchanges happen on the gateway's _outbound_ side. A
client doesn't need any XAA support to benefit from it.

:::

## What the gateway does for you

In the XAA flow the gateway plays the requesting app, so the MCP client and the
upstream MCP server are each insulated from the protocol:

- **The MCP client** does nothing new. It runs the same MCP OAuth flow it would
  for any gateway route. Claude, Cursor, ChatGPT, VS Code, and any
  spec-compliant client work unchanged.
- **The two-exchange dance** — the RFC 8693 token exchange at the IdP and the
  RFC 7523 JWT-bearer redemption at the upstream — runs inside the gateway. You
  configure endpoints and credentials; the gateway mints, redeems, caches, and
  refreshes.
- **Per-user identity** is preserved end to end. The IdP sees the specific user,
  the upstream sees a token minted for that user, and gateway analytics record
  the same subject.

## Glossary

- **Identity assertion** — Proof the IdP authenticated the user, carried as an
  OIDC ID token (`urn:ietf:params:oauth:token-type:id_token`) or SAML 2.0
  assertion. It's the _input_ to the XAA flow.
- **ID-JAG (Identity Assertion JWT Authorization Grant)** — A short-lived signed
  JWT the IdP mints, audience-restricted to one resource authorization server.
  Used once to obtain an access token there. JWT header `typ: oauth-id-jag+jwt`;
  token type `urn:ietf:params:oauth:token-type:id-jag`. It is **not** an access
  token.
- **Identity provider (IdP)** — The SSO authority both apps trust (Okta, Entra,
  Auth0). In XAA it also brokers cross-app access by issuing ID-JAGs after
  evaluating policy. Identified by `iss` in the ID-JAG.
- **Resource authorization server** — The _target_ app's OAuth server. It
  consumes the ID-JAG via the JWT-bearer grant and issues the real access token.
  Identified by `aud` in the ID-JAG. In Zuplo's outbound flow, this is the
  upstream's server, not the gateway.
- **Requesting app** — The party that obtains and redeems the ID-JAG. When the
  gateway fronts an XAA-protected upstream, the gateway is the requesting app.
- **Token exchange (RFC 8693)** — The grant used at the IdP to swap the identity
  assertion for the ID-JAG.
- **JWT bearer grant (RFC 7523)** — The grant used at the resource authorization
  server to redeem the ID-JAG for an access token.

## When to use XAA

Reach for XAA when:

- An upstream MCP server is protected by an **enterprise resource authorization
  server** that accepts ID-JAGs (the "enterprise-managed authorization"
  pattern), and you want the gateway to broker access through your IdP rather
  than running a separate OAuth client per upstream.
- You need **central governance** — one place in the IdP to grant, audit, and
  revoke which apps can reach which APIs on a user's behalf.

For an upstream that uses plain per-user or shared OAuth (Linear, Notion,
Stripe, and most SaaS MCP servers today), use
[upstream OAuth](../auth/upstream-oauth.mdx) instead — XAA isn't required.

## Try it and configure it

- [Quickstart](./quickstart.mdx) — see XAA work end to end in minutes using
  Okta's hosted [xaa.dev](https://xaa.dev) playground, with no identity tenant
  required.
- [Configuration reference](./policy-reference.mdx) — every `idJag` option on
  the token-exchange policy.

## Learn more

- [Identity Assertion JWT Authorization Grant (IETF draft)](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/)
  — the ID-JAG specification. Note this is an active draft, not yet a finalized
  RFC.
- [MCP authorization spec](https://modelcontextprotocol.io/specification/latest/basic/authorization)
  and the
  [Enterprise-Managed Authorization extension](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization)
  — where ID-JAG plugs into MCP.
- [Okta — Cross App Access](https://developer.okta.com/blog/2025/09/03/cross-app-access)
  — Okta's explainer and walkthrough.
- [How the MCP Gateway works](../how-it-works.mdx) — the gateway's two OAuth
  surfaces and request lifecycle.
