Let Customers Manage Their Agents

Note

If you instead run the agents and need them to call third-party MCP servers, see Manage agents in your enterprise.

Use this guide when you host an MCP server for your customers (the B2B MCP case) and each enterprise customer should govern their own users and agents with their XAA-compatible workforce IdP.

Customers keep using Okta, Ping, or their own Descope project, as their XAA-enabled workforce IdP. Their IT decides which agents may reach your server. Descope, as the authorization server for the MCP server you protect with Descope MCP Auth, accepts that customer's XAA token (ID-JAG), validates it against the issuer you trusted for that tenant, and mints an ordinary Descope access token for your server. Your MCP server does not need to understand ID-JAG.

You do not provision the customer's workforce users for this path; their admin controls access in their IdP, and your MCP server honors what it decides. For the protocol, see How XAA and ID-JAG work.

How It Works

The premise is that two organizations each decide the half of the question they actually own. The customer's IdP decides whether one of their agents may reach your MCP server at all, because only they know their own employees and their own policies. Descope, sitting in front of that server, decides what that agent gets once it arrives, because only you know your server's scopes.

Cross App Access is the handoff between those two decisions. The customer's IdP writes its decision into a short-lived signed assertion, the ID-JAG, and Descope verifies that assertion before honoring it.

However the customer's agent obtained its XAA token, the part you configure is the same. The agent presents that token to your Descope-protected MCP server. Descope matches the assertion's subject to the user in the matching tenant, and mints an access token for your server.

This works the same whether the XAA token came from Okta, Ping, or any other workforce IdP the customer uses that supports Cross App Access / ID-JAG.

Note

The customer's workforce IdP owns the first authorization decision: whether a given client may request cross-app access to your MCP server at all, through its own Cross App Access configuration.

Descope owns the second: which scopes the resulting Descope token carries. See Agent Authorization for how those scopes are resolved.

Access goes through Descope first
MCP Clientholds the ID-JAGDescopeAuthorization serverfor your MCP serverYour MCP ServerResource (Descope MCP Auth)1. Present ID-JAGJWT Bearer grant2. Issue access_tokenafter validate + match subjectOnly then can the client reach your MCP server3. Call with Bearer access_tokenyour server validates a normal Descope token
Presenting ID-JAG to Descope…
  1. The MCP client presents the customer's ID-JAG to Descope (the authorization server for your MCP server).
  2. Descope validates it, matches the subject to the tenant user, and returns a Descope access_token.
  3. Only then does the client call your MCP server with that token. Your server never sees the ID-JAG.

Your MCP server is the Resource

The target being protected is your own MCP server, registered in Descope as an MCP Server Resource. Its URL is the audience the customer's IdP names when it mints an ID-JAG, and it is what Descope issues the final access token for. Your server validates that token exactly as it does for any other request.

Enable Cross-App Access per Tenant

Each enterprise customer is a tenant in your Descope project. Under a tenant, you register that customer's workforce IdP as a trusted issuer whose ID-JAGs Descope will accept for your MCP server.

Cross-App Access is part of the tenant's SSO configuration, because the assertions you accept come from the same workforce IdP the tenant already signs in with. It reuses the JWT Bearer grant: you add the issuer's URL and keys, and Descope accepts assertions signed by it.

Because trust is scoped to the tenant, each customer's users flow through their own IdP, and an ID-JAG minted for one customer can never buy access for another.

Two Ways to Configure XAA

The configuration can be written either by you or by the customer, and both produce the same tenant settings:

PathWho does itWhere
ConsoleYou, on the customer's behalfYour tenant, then Authentication Methods → SSO → Cross-App Access
SSO Setup SuiteThe customer's IT admin, self-serviceThe Cross App Access section, alongside SSO and SCIM

The Setup Suite path is usually what you want for a B2B product, since the customer configures Cross-App Access for their own organization in the same session where they set up SSO, without a support ticket.

Note

Whether the Cross App Access (XAA) section appears in the SSO Setup Suite, is controlled by the SSO Suite Features setting.

Turning it on

In the Console, open the tenant, go to Authentication Methods → SSO, and select the Cross-App Access tab. Enable Allow Cross-App Access (ID-JAG), which is off by default. Turning it on reveals the sections below.

allow cross-app access (xaa) toggle off

The tab has three parts, and they run in order.

  • Resource Server Details gives you values to hand to the customer.
  • Trusted Issuer is where you accept their IdP.
  • JIT Provisioning decides how their users become users in your project.

Resource Server Details

This section is read-only.

Note

The customer's IdP must send the tenant ID in the aud_tenant claim. Descope uses it to work out which tenant's Cross-App Access configuration an incoming assertion should be evaluated against.

If aud_tenant is missing, or carries a value different from the one shown here, token validation fails. This is the most common reason a correctly signed ID-JAG is still rejected.

It shows the two values that identify your resource server to the customer's IdP, and the customer copies both into the cross-app access configuration on their side.

ValueExampleWhere it lands
Audience__BaseURL__/v1/apps/__ProjectID__The aud claim of the ID-JAGs their IdP mints
Tenant IDT2fmCOB4Ps5bBPnN4EVAb4cNdjPXThe aud_tenant claim of those same ID-JAGs

Together the two values are what keep customers separated. aud says the assertion is for your project, and aud_tenant says which customer inside it.

An ID-JAG minted for one tenant cannot be redeemed against another.

Resource Server Details section in the Cross-App Access (XAA) configuration

Trusted Issuer

Which IdP Descope accepts assertions from, and how it verifies their signatures.

An ID-JAG is only worth as much as the issuer behind it, so this is the setting that carries the trust. The customer gives you their IdP's issuer URL, and Descope uses it two ways. It is the value every incoming assertion's iss claim has to match, and it is where Descope fetches the public keys that verify the assertion's signature.

On each incoming assertion Descope then checks that the signature verifies against those keys, that iss is the issuer registered on this tenant, that aud matches the Audience you configured, and that the assertion has not expired. An assertion that fails any of these is rejected and no access token is issued.

Registering the issuer on a specific tenant is what keeps customers apart. An assertion is only ever evaluated against the issuer configured for the tenant it targets, so an ID-JAG minted by one customer's Okta cannot buy access anywhere else in your project no matter how valid its signature is.

FieldRequiredDescription
Issuer URLYesThe expected iss claim on incoming ID-JAGs. If the issuer is discoverable, saving the configuration fetches the JWKs URL and the rest of the metadata automatically.
JWKs URLOnly if the issuer is not discoverableThe issuer's JSON Web Key Set, used to fetch the public keys that verify the JWT signature. You can set it manually, otherwise Descope attempts to discover it from the Issuer URL.
Sign AlgorithmNoThe algorithm used to verify the JWT signature, for example RS256 or ES256. Left blank, the algorithm is read from the token header.
User Information Endpoint URLNoAn endpoint called after the token validates, to fetch additional attributes about the subject user that are not carried in the JWT.

A tenant can trust more than one issuer. The + Add issuer button at the bottom of the section adds another set of these fields, which is what you would use when a customer runs more than one IdP, or is midway through moving between them. Descope accepts an assertion if it matches any issuer configured on the tenant.

The Trusted Issuer section in the Cross-App Access (XAA) configuration

JIT Provisioning

The assertion names a subject, but that subject is a user in the customer's directory, not yet a user in yours. Something has to create the Descope user record that the access token is eventually issued for, and you have two ways to do it.

Enable JIT Provisioning is a toggle that creates and updates that user from the claims in the ID-JAG, every time one validates. The first time a given employee's agent calls your server the user appears in the tenant, and later assertions keep their attributes current. Nothing has to be set up in advance, which is why JIT is the quicker path to a working integration.

With the toggle off, provisioning and mapping have to happen some other way, in practice through SCIM.

Leaving it off means the exchange only succeeds for users who already exist in the tenant.

The JIT Provisioning section in the Cross-App Access (XAA) configuration

Choosing between JIT and SCIM

The tradeoff is not about how users get created. It is about what happens when they leave.

JIT provisioningSCIM provisioning
User createdOn the first ID-JAG exchangePushed by the customer's IdP ahead of time
Setup requiredNoneThe customer configures SCIM on their tenant
OffboardingNothing tells Descope the user is goneThe IdP pushes the deactivation to Descope

JIT only ever hears about a user when that user shows up. It has no channel for the opposite event. When an employee leaves the company and their IdP account is disabled, their agent stops being able to obtain new ID-JAGs, but the Descope user record you created from an earlier assertion stays active, and so does any Descope session already issued for it.

SCIM is the channel for that event. The customer's IdP pushes the deactivation, Descope marks the user inactive, and the next time that user's session tries to refresh, the refresh fails and access ends. That is what makes SCIM the right answer for enterprise customers who expect offboarding in their directory to actually revoke access to your product.

Note

A deactivation takes effect on the next login or token refresh, so an access_token already in an agent's hands remains valid until it expires.

Shorten your session and refresh token durations if you need a tighter window, and revoke the session directly for an immediate cutoff. See SCIM session behavior, for more details.

So: enable JIT when you do not manage that tenant's users with SCIM, and it is the reasonable default for getting a customer running.

When SCIM provisions the tenant's users, leave JIT off and let SCIM own the user record, so that the lifecycle events you care about arrive through the same channel that created the user.

Note

Entra pushes SCIM changes on a roughly 40 minute cycle, and disables in particular often wait for a provisioning job that includes that user. If a customer needs a deactivation reflected immediately, they can run Provision on Demand in Entra. See SCIM best practices.

User Attribute Mapping

This is the same attribute mapping you already know from SSO, with one difference in where the values come from. Instead of reading a SAML assertion or an id_token, you are mapping claims carried on the ID-JAG onto Descope user attributes.

It matters most when the customer's IdP sends custom claims in the assertion that you want reflected on the user profile, since JIT builds the user from exactly these claims.

The User Attribute Mapping section in the Cross-App Access (XAA) configuration

SCIM does not use these mappings

SCIM provisions users according to the existing SCIM and SSO configuration and mapping. It does not read the ID-JAG claims mapped here, so these mappings only take effect on the JIT path.

Group Attribute Mapping

Here you enter the name of the claim that carries group association, commonly groups. Descope reads group names out of that claim on the ID-JAG and maps them onto the tenant's groups.

This lets a customer's existing IdP group membership drive access without a separate assignment step in your product.

The Group Attribute Mapping section in the Cross-App Access (XAA) configuration

Note

Not every IdP sends groups in an ID-JAG. Okta, for example, does support sending group values in the assertion, so check what your customer's IdP emits before relying on this mapping.

Setup Instructions

Protect your MCP server

Create the MCP Server Resource and stand up Descope MCP Auth as you would for any MCP server you operate.

Add a tenant per customer

Represent each enterprise customer as a tenant.

Turn on Cross-App Access and register the issuer

On that tenant, go to Authentication Methods → SSO → Cross-App Access and enable Allow Cross-App Access (ID-JAG). Under Trusted Issuer, add the customer IdP's Issuer URL so Descope accepts its assertions, using + Add issuer if they run more than one.

Add a JWKs URL as well if the issuer is not discoverable. See Enable Cross-App Access per Tenant for every field.

Decide how the tenant's users are provisioned

Turn on JIT Provisioning if SCIM is not managing that tenant's users, which is the quickest way to get a customer working. If the customer provisions users with SCIM, leave JIT off so deactivations in their directory also reach Descope and end access to your product.

See Choosing between JIT and SCIM for what that choice costs you at offboarding time.

Hand the resource server details to the customer

Copy the Audience and Tenant ID from Resource Server Details and give both to the customer, who enters them in the cross-app access configuration of their IdP.

Their IdP has to send the tenant ID in the aud_tenant claim. A missing or mismatched aud_tenant causes validation to fail even when everything else is correct.

The customer's IT admin can do all of this themselves through the Cross App Access section of the SSO Setup Suite.

Write policies

Use Policies to decide which scopes the exchanged token carries, keyed on user.roles, user.tenantIds, and claims carried over from the assertion.

Was this helpful?

On this page