# Cross App Access configuration reference

Cross App Access is configured on the
[`mcp-token-exchange-inbound`](#gateway-as-requesting-app) policy. Setting
`authMode: "id-jag"` and providing an `idJag` block makes the gateway act as the
XAA **requesting app**: it mints an ID-JAG from your IdP and redeems it at an
upstream resource authorization server. This is the configuration the
[quickstart](./quickstart.mdx) uses.

:::note

The authoritative source for these options is the policy's runtime schema. The
generated `mcp-token-exchange-inbound` reference page predates `id-jag` mode;
the options below reflect the runtime behavior.

:::

## Gateway as requesting app

Set `authMode: "id-jag"` on a `mcp-token-exchange-inbound` policy and provide an
`idJag` block. Attach the policy to the upstream route after the inbound MCP
OAuth policy.

```jsonc title="config/policies.json"
{
  "name": "id-jag-upstream",
  "policyType": "mcp-token-exchange-inbound",
  "handler": {
    "module": "$import(@zuplo/runtime/mcp-gateway)",
    "export": "McpTokenExchangeInboundPolicy",
    "options": {
      "displayName": "Upstream",
      "authMode": "id-jag",
      "idJag": {
        "scopes": ["mcp:tools"],
        "scopeDelimiter": " ",
        "idp": {
          "tokenUrl": "https://idp.example.com/token",
          "clientAuth": {
            "method": "client_secret_post",
            "clientId": "$env(IDP_CLIENT_ID)",
            "clientSecret": "$env(IDP_CLIENT_SECRET)",
          },
        },
        "resourceAs": {
          "tokenUrl": "https://upstream.example.com/token",
          "audience": "https://upstream.example.com",
          "resource": "https://upstream.example.com/mcp",
          "clientAuth": {
            "method": "client_secret_post",
            "clientId": "$env(RESOURCE_AS_CLIENT_ID)",
            "clientSecret": "$env(RESOURCE_AS_CLIENT_SECRET)",
          },
        },
      },
    },
  },
}
```

### idJag options

| Option           | Type       | Default | Description                                                          |
| ---------------- | ---------- | ------- | -------------------------------------------------------------------- |
| `scopes`         | `string[]` | `[]`    | Scopes requested in both exchanges.                                  |
| `scopeDelimiter` | `string`   | `" "`   | Delimiter used to join scopes.                                       |
| `idp`            | object     | —       | Where the gateway mints the ID-JAG (RFC 8693 token exchange).        |
| `resourceAs`     | object     | —       | Where the gateway redeems the ID-JAG for an access token (RFC 7523). |

### idp

The identity provider that issues the ID-JAG.

| Option       | Type   | Description                                           |
| ------------ | ------ | ----------------------------------------------------- |
| `tokenUrl`   | string | The IdP token endpoint.                               |
| `clientAuth` | object | How the gateway authenticates to the IdP (see below). |

### resourceAs

The upstream's resource authorization server that issues the access token.

| Option       | Type   | Description                                                                                                           |
| ------------ | ------ | --------------------------------------------------------------------------------------------------------------------- |
| `tokenUrl`   | string | The resource authorization server's token endpoint.                                                                   |
| `audience`   | string | **Required.** The resource AS identifier; sent as the token-exchange `audience` and becomes the ID-JAG `aud`.         |
| `resource`   | string | Optional [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) resource indicator. Defaults to the route's upstream URL. |
| `clientAuth` | object | How the gateway authenticates to the resource AS (see below).                                                         |

### clientAuth

Both `idp.clientAuth` and `resourceAs.clientAuth` take the same shape. The
`method` selects how the gateway authenticates:

```jsonc
// client_secret_post (or client_secret_basic)
"clientAuth": {
  "method": "client_secret_post",
  "clientId": "...",
  "clientSecret": "$env(...)"
}
```

```jsonc
// private_key_jwt
"clientAuth": {
  "method": "private_key_jwt",
  "clientId": "...",
  "privateKeyPem": "$env(...)",
  "algorithm": "RS256",
  "keyId": "...",
  "expiresInSeconds": 300
}
```

| Option             | Type   | Default | Applies to        | Description                                                        |
| ------------------ | ------ | ------- | ----------------- | ------------------------------------------------------------------ |
| `method`           | enum   | —       | all               | `client_secret_post`, `client_secret_basic`, or `private_key_jwt`. |
| `clientId`         | string | —       | all               | The OAuth client ID.                                               |
| `clientSecret`     | string | —       | secret methods    | The OAuth client secret.                                           |
| `privateKeyPem`    | string | —       | `private_key_jwt` | PEM private key used to sign the client-assertion JWT.             |
| `algorithm`        | enum   | `RS256` | `private_key_jwt` | `RS256`/`RS384`/`RS512`/`ES256`/`ES384`/`ES512`.                   |
| `keyId`            | string | —       | `private_key_jwt` | Optional `kid` header on the client assertion.                     |
| `audience`         | string | —       | `private_key_jwt` | Optional audience override for the client assertion.               |
| `expiresInSeconds` | number | `300`   | `private_key_jwt` | Client-assertion lifetime, max `3600`.                             |

### What the gateway does at request time

On a tool call to the route, the gateway:

1. Resolves the user's stored IdP identity assertion (bound during the inbound
   browser login). If absent or expired, it returns a connect-required error and
   refreshes the subject token when it can.
2. Runs an RFC 8693 token exchange at `idp.tokenUrl`, requesting an ID-JAG
   audience-restricted to `resourceAs.audience`.
3. Redeems the ID-JAG at `resourceAs.tokenUrl` as an RFC 7523 JWT-bearer grant
   to get the upstream access token.
4. Caches the upstream token per user and forwards the tool call with
   `Authorization: Bearer <upstream token>`.

## Notes and limitations

- The gateway issues **opaque** access tokens, not JWTs.
- **DPoP is not supported.** Requests with a `DPoP` header are rejected.
- XAA requires a prior inbound browser login so the gateway has an IdP identity
  assertion to exchange.

## Related

- [Overview](./overview.mdx) — the protocol and the gateway's role.
- [Quickstart](./quickstart.mdx) — a working `id-jag` configuration on the
  playground.
- [`mcp-token-exchange-inbound` policy](/policies/mcp-token-exchange-inbound) —
  the base token-exchange policy reference.
