> ## Documentation Index
> Fetch the complete documentation index at: https://stytch.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross App Access (XAA) Flow

> Exchange a JWT from an external workforce identity provider for a Stytch Connected Apps access token without browser interaction.

<Note>
  The Cross App Access Flow is only applicable for multi-tenant auth uses of Connected Apps.
</Note>

The Cross App Access (XAA) Flow allows a Connected App to exchange a JWT issued by an external workforce identity provider (IdP) for a Stytch access token. This enables service-to-service and agent-to-service flows where the user has already authenticated with their organization's IdP (e.g. Okta, Microsoft Entra, Google Workspace) and needs to access resources protected by Stytch Connected Apps — without a browser-based redirect.

This flow uses the `urn:ietf:params:oauth:grant-type:jwt-bearer` grant type and implements the [Identity Assertion Authorization Grant (ID-JAG)](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/) draft specification.

## When to use the XAA Flow

Use the Cross App Access Flow when:

* Your application needs to act on behalf of users who have already authenticated with a workforce IdP
* You want to avoid browser redirects (e.g. in CLI tools, background services, or AI agents)
* Your users sign in via OIDC SSO and you want Connected Apps to recognize them without a separate authorization code flow

## Prerequisites

* A B2B Stytch project with [Connected Apps](/connected-apps/overview) enabled
* An [OIDC SSO connection](/multi-tenant-auth/authentication/sso/overview) configured for the organization, pointing to the external IdP
* A **Confidential** Connected App client (first-party or third-party)

## How it works

<Steps>
  <Step title="User authenticates with the workforce IdP">
    The user authenticates with their organization's workforce identity provider (e.g. Okta, Microsoft Entra) via OpenID Connect. After authentication, the Connected App receives an ID token representing the user's session with the IdP.
  </Step>

  <Step title="Connected App requests an ID-JAG from the IdP">
    The Connected App makes a [Token Exchange](https://datatracker.ietf.org/doc/html/rfc8693) request to the workforce IdP's token endpoint to obtain an ID-JAG token:

    ```bash theme={null}
    curl -X POST https://your-idp.example.com/oauth2/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
      -d "requested_token_type=urn:ietf:params:oauth:token-type:id-jag" \
      -d "subject_token=<ID_TOKEN>" \
      -d "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
      -d "audience=<STYTCH_PROJECT_ISSUER_URL>" \
      -d "scope=openid email profile"
    ```

    The IdP validates the subject token, evaluates any administrator-defined policies, and issues a signed ID-JAG token with `typ: "oauth-id-jag+jwt"` in the header.
  </Step>

  <Step title="Connected App exchanges the ID-JAG for a Stytch access token">
    The Connected App sends the ID-JAG token to Stytch's [token endpoint](/api-reference/b2b/api/connected-apps/exchange-id-jag) using the XAA Flow's grant type:

    ```bash theme={null}
    curl -X POST https://<tenant>.customers.stytch.dev/v1/oauth2/token \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
      -d "assertion=<ID_JAG_TOKEN>" \
      -d "client_id=<CONNECTED_APP_CLIENT_ID>" \
      -d "client_secret=<CONNECTED_APP_CLIENT_SECRET>" \
      -d "scope=openid email profile"
    ```
  </Step>

  <Step title="Stytch validates and issues access token">
    Stytch validates the ID-JAG token by:

    1. Matching the token's `iss` claim to a configured OIDC connection
    2. Verifying the token signature against the OIDC connection's JWKS endpoint
    3. Validating that `aud` contains the Stytch project's issuer URL
    4. Confirming the `client_id` claim matches the requesting Connected App
    5. Resolving the token's `sub` claim to a member (see below)

    If all checks pass, Stytch issues a Connected Apps access token scoped to the resolved member.
  </Step>
</Steps>

## Member resolution

Stytch resolves the ID-JAG token's `sub` claim to a member using two mechanisms, checked in this order:

1. **OIDC registration match**: Stytch looks for an active OIDC member registration on the same connection where the `provider_subject` matches the token's `sub`. This is automatically created when a member signs in via OIDC SSO — no additional configuration is needed.

2. **External ID match**: If no OIDC registration is found, Stytch checks for a member in the organization whose `external_id` matches the token's `sub`.

<Info>
  If your members sign in via OIDC SSO before using the XAA Flow, they will be resolved automatically through their OIDC registration. No manual `external_id` mapping is required.
</Info>

## ID-JAG token format

The ID-JAG token must be a signed JWT with the following structure:

**Header:**

```json theme={null}
{
  "alg": "RS256",
  "typ": "oauth-id-jag+jwt",
  "kid": "<KEY_ID>"
}
```

**Claims:**

| Claim       | Description                                                    |
| ----------- | -------------------------------------------------------------- |
| `iss`       | The IdP's issuer URL (must match the OIDC connection's issuer) |
| `sub`       | The user's identifier at the IdP                               |
| `aud`       | Must include your Stytch project's issuer URL                  |
| `client_id` | The Connected App's client ID                                  |
| `scope`     | Requested scopes (space-delimited)                             |
| `exp`       | Expiration time                                                |
| `iat`       | Issued-at time                                                 |
| `jti`       | Unique token identifier                                        |

## Token exchange response

A successful exchange returns a standard OAuth 2.0 token response:

```json theme={null}
{
  "access_token": "eyJ...",
  "token_type": "bearer",
  "expires_in": 3600,
  "scope": "openid email profile"
}
```
