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

# Okta Cross App Access (XAA)

> Let AI agents connect to Opal's MCP servers on behalf of your Okta users, without an interactive sign-in.

<Warning>
  Okta Cross App Access for Opal's MCP servers is currently in limited beta and may change. Contact Opal support to enable it for your organization.
</Warning>

[Okta Cross App Access (XAA)](https://developer.okta.com/docs/concepts/xaa/) lets an AI agent registered in your Okta tenant connect to Opal's MCP servers as one of your users, without that user signing in to Opal or approving a consent screen. Your Okta admin decides which agents can reach Opal, and Opal applies the user's normal Opal permissions to everything the agent does.

The flow has two hops:

1. The agent exchanges the user's Okta token for a short-lived **ID-JAG** (Identity Assertion JWT Authorization Grant) issued by your Okta org.
2. The agent sends the ID-JAG to Opal's token endpoint and receives an Opal access token for the MCP servers.

## Prerequisites

* Okta Cross App Access enabled for your organization in Opal. Contact Opal support.
* An Okta tenant with Cross App Access and AI agents available, and an Okta admin.
* An Opal admin.
* Each user the agent acts for exists in Opal, with the same primary email address as their Okta profile. Opal doesn't create users through Cross App Access.

## Step 1: Connect your Okta tenant in Opal

1. In Opal, go to **Settings → Authentication → Okta Cross App Access** and click **Configure**.
2. Set **Okta Issuer URL** to your Okta org's issuer, for example `https://your-org.okta.com`. Use the org itself, not a custom authorization server such as `https://your-org.okta.com/oauth2/default`.
3. Make sure **Connection enabled** is on.
4. Copy the **Okta Audience/tenant ID** value. This is your Opal organization ID, and you'll paste it into Okta in step 3.
5. Click **Submit**.

## Step 2: Register the agent in Opal

Opal authenticates the agent with its own client credentials when the agent redeems an ID-JAG.

1. Under **Settings → Authentication → Okta Cross App Access requesting apps**, click **Add requesting client**.
2. Enter a **Label** that identifies the agent, such as its Okta name.
3. Copy the **Client ID** and **Client secret**. The secret is shown only once.

To cut off an agent later, click **Revoke** next to its client. Revoking stops the agent from getting new Opal access tokens.

## Step 3: Add Opal as a resource app in Okta

1. In the Okta Admin Console, go to **Applications → Applications → Create App Integration**, choose **OIDC - OpenID Connect** and **Web Application**, and create the app with the **Authorization Code** grant. The sign-in redirect URI isn't used by Cross App Access, so a placeholder is fine.
2. Assign the users the agent will act for to this app.
3. Open the app's **Resource Server** tab, edit **Cross-app access (XAA)**, and enable it with these values:

| Field | Value |
| - | - |
| Issuer URL | `https://opal.dev/mcp` |
| Audience/tenant ID | Your Opal organization ID, copied in step 1 |

<Note>
  The Audience/tenant ID tells Opal which Opal organization an ID-JAG is for. Several Opal organizations can use the same Okta tenant, each with its own resource app and its own organization ID.
</Note>

## Step 4: Connect the AI agent to Opal in Okta

1. In the Okta Admin Console, go to **Directory → AI Agents** and open your agent. Under **User access**, link it to the app your users sign in to the agent with.
2. Under **Resource connections**, click **Add resource connection**, choose **Application**, and select the Opal resource app from step 3.
3. Set the client ID to the **Client ID** from step 2.
4. Choose the scopes the agent may use. See [Scopes](#scopes).
5. Make sure the agent's status is **Active**.

## Step 5: Redeem the ID-JAG for an Opal access token

These details are for whoever builds or configures the agent. When the agent requests an ID-JAG from your Okta org's token endpoint (`https://your-org.okta.com/oauth2/v1/token`), it sets `audience` to `https://opal.dev/mcp` and `scope` to the Opal scopes it needs.

The agent then sends the ID-JAG to Opal's token endpoint, authenticating with the client ID and secret from step 2 (HTTP Basic or `client_id` and `client_secret` in the body):

```bash theme={null}
curl -X POST https://app.opal.dev/token \
  -u "$OPAL_XAA_CLIENT_ID:$OPAL_XAA_CLIENT_SECRET" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  --data-urlencode "assertion=$ID_JAG"
```

Opal responds with an access token:

```json theme={null}
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "mcp:readonly"
}
```

The agent passes it as an `Authorization: Bearer` header to any of Opal's [MCP servers](/docs/mcp/overview), for example `https://app.opal.dev/mcp/end-user`. Self-hosted deployments replace `https://app.opal.dev` with their own domain.

Opal doesn't issue refresh tokens for Cross App Access. When the access token expires, the agent redeems its ID-JAG again, or gets a new one from Okta. The access token lifetime follows the **Settings → Authentication → MCP OAuth token lifetime** setting, which defaults to 1 hour.

## Scopes

Opal never grants more than the scopes in the ID-JAG:

| Scopes in the ID-JAG | Access granted |
| - | - |
| `mcp` | Full access, unless the agent asks Opal for `mcp:readonly` |
| `mcp:readonly` only | Read-only access |
| Neither | Rejected with `invalid_scope` |

Full access means the user's complete Opal permissions, read and write. Read-only access rejects every write, as described in [Scopes](/docs/mcp/overview#scopes).

## Troubleshooting

| Error from Opal | Common causes |
| - | - |
| `invalid_client` | The client ID or secret is wrong, or the client was revoked. |
| `invalid_grant` | The ID-JAG expired; its issuer doesn't match step 1; the Okta Issuer URL or Audience/tenant ID in step 3 is wrong; the Okta resource connection uses a different client ID than step 2; or no Opal user has the ID-JAG's email. |
| `invalid_scope` | The ID-JAG contains neither `mcp` nor `mcp:readonly`. |

## Beta limitations

* Okta is the only supported identity provider. The issuer must be an Okta org on `okta.com`, `oktapreview.com`, or `okta-emea.com`; Okta custom domains and Okta for Government aren't supported.
* Users are matched by primary email address. Opal system users and service users can't be used.
* Each Opal organization connects one Okta issuer.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.