---
title: Enterprise-Managed Authorization (EMA)
product: vercel
url: /docs/connect/ema
canonical_url: "https://vercel.com/docs/connect/ema"
last_updated: 2018-10-20
type: how-to
prerequisites:
  - /docs/connect
related:
  - /docs/passport/read-identity
  - /docs/connect/ts-sdk-reference
  - /docs/connect/ema/slack
  - /docs/connect/ema/linear
  - /docs/connect/quickstart
summary: Use Enterprise-Managed Authorization (EMA) in Vercel Connect to access services on behalf of signed-in users through your identity provider.
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

# Enterprise-Managed Authorization (EMA)

Use Enterprise-Managed Authorization (EMA) to access services on behalf of a signed-in user, under your organization's policies. After administrator approval, users don't need separate consent at each service.

Pass your [Passport identity](/docs/passport/read-identity) to [`getToken`](/docs/connect/ts-sdk-reference#gettoken). Vercel Connect exchanges the user's identity provider (IdP) refresh token for an **Identity Assertion JWT Authorization Grant (ID-JAG)**, then redeems it for a service access token. Connect handles caching and renewal.

If your app manages sign-in without Passport, use the [custom integration flow](#custom-integrations-without-passport).

## EMA and cross-app access (XAA)

EMA is a [Model Context Protocol (MCP) authorization extension](https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization). It lets administrators control access to MCP servers through an enterprise IdP.

EMA applies **cross-app access (XAA)** to MCP clients and servers. XAA describes the broader authorization flow, which uses the same ID-JAG token exchange. Your app still acts on behalf of a signed-in user, within the permissions approved by the IdP and resource service.

Configure the same flow under each provider's name:

- **Vercel Connect:** Enable **Enterprise-Managed Authorization (EMA)** on the IdP and resource connectors.
- **Slack:** Enable [Enterprise-Managed Authorization](https://docs.slack.dev/authentication/enterprise-managed-authorization/) for access to the Slack MCP server.
- **Okta:** Configure [Cross App Access (XAA)](https://developer.okta.com/docs/concepts/xaa/) between the requesting app and resource app.

Connect's API fields and configuration structures still use `xaa`. The EMA label doesn't change the token exchange or require a separate XAA setting.

Enabling EMA in Connect doesn't replace provider-side approval. For provider-specific requirements, follow the [Slack setup guide](/docs/connect/ema/slack) or [Linear setup guide](/docs/connect/ema/linear).

## Prerequisites

- Configure the SDK and project authentication using the [quickstart](/docs/connect/quickstart).
- Use an IdP and resource service that support EMA's ID-JAG token exchange, with administrator-approved access policies.

## Configure your identity provider

Register an OpenID Connect (OIDC) application with authorization code and refresh token grants. Add `https://connect.vercel.com/callback` as a sign-in redirect URI and assign the intended users or groups.

Create an OAuth connector with that application's client ID and credentials. Store credentials in the connector, not your application code.

If you already use Passport, reuse its OAuth connector and OIDC application.

For Okta:

1. [Register and activate an AI agent](https://developer.okta.com/docs/guides/xaa-agent-to-app/main/) linked to your OIDC application.
2. Enable XAA on each resource application and connect it to the agent. Allow only the required scopes.
3. Use the **org authorization server**, not a custom server such as `/oauth2/default`. See [Okta's token exchange reference](https://developer.okta.com/docs/guides/xaa-request-token-ex/openidconnect/main/).

## Enable EMA on your connectors

Open each connector's **Settings**, enable **Enterprise-Managed Authorization (EMA)**, and select its role:

| Connector | Mode | Purpose |
| --- | --- | --- |
| Identity provider | **Identity Provider (IdP)** | Issues an ID-JAG for the signed-in user. |
| Target service | **Resource** | Exchanges the ID-JAG for a service access token. |

Set the resource connector's **Audience** and **Resource** to the provider's required values, or leave them empty to use connector defaults. Save both connectors.

These settings don't grant access in the provider's admin console. Configure both sides before requesting tokens.

## Use EMA with Passport

[Configure Passport for your project](/docs/passport/set-up-identity-provider#configure-passport-for-a-project) with the EMA-enabled IdP connector. Include `openid` and `offline_access` in the connector's [User Authorization scopes](/docs/passport/additional-identity-scopes#add-a-scope-to-the-connect-application) so Connect can store an IdP refresh token. Start a new Passport session after changing these settings.

Install the SDKs in your app:

```bash package-manager
pnpm add @vercel/connect @vercel/passport
```

### Request a resource token

In server-side code, call `requireIdentity()` and pass the returned identity as `subject`. Passport identifies the IdP connector and session, so you don't need `experimental_idp`.

This Next.js helper requests a token for the current Passport user. Replace `oauth/resource` with your resource connector's UID and pass the scopes your app needs:

```ts filename="lib/resource-token.ts"
import 'server-only';
import { getToken } from '@vercel/connect';
import { requireIdentity } from '@vercel/passport';

export async function getResourceToken(scopes: string[]) {
  const identity = await requireIdentity();

  return getToken('oauth/resource', { subject: identity, scopes });
}
```

Use the returned token only for server-side resource requests. Request scopes through `getToken`, not the EMA settings. Both providers must allow them.

Test with a [Passport-protected deployment](/docs/passport/read-identity#test-the-real-passport-flow). The local development identity has no token, so `requireIdentity()` throws `PassportIdentityError` instead of using it for EMA.

## Custom integrations without Passport

Use this flow only if your app manages user sign-in without Passport. Authorize the user through the IdP connector, then set `subject.experimental_idp` on resource token requests.

### Authorize the user with the IdP

Use the [user authorization flow](/docs/connect/quickstart#write-your-code) with the IdP connector, requesting `openid` and `offline_access`. Connect needs a stored, usable refresh token with `openid` scope before it can request resource tokens.

Reuse the same `subject.id` and optional `subject.issuer` on resource requests. These identify your application user; don't substitute the resource provider's user ID.

### Request a resource token with a custom subject

Set the experimental `subject.experimental_idp` field to your IdP connector's ID or UID.

This server-side helper takes your authenticated user's ID and required scopes. Replace both connector UIDs with yours:

```ts filename="lib/custom-resource-token.ts"
import { getToken } from '@vercel/connect';

export async function getResourceToken(userId: string, scopes: string[]) {
  const subject = {
    type: 'user' as const,
    id: userId,
    experimental_idp: 'oauth/okta',
  };

  return getToken('oauth/resource', { subject, scopes });
}
```

Read `userId` from your authenticated server session, not client input. Use the returned token only for server-side resource requests.

## Handle authorization failures

If Connect throws `UserAuthorizationRequiredError`:

- **Passport session:** If its stored IdP refresh token is missing or revoked, sign in through Passport again. Check that the IdP application allows refresh tokens.
- **Custom integration:** Reauthorize the IdP with the same user ID and issuer, without `experimental_idp`, requesting `openid` and `offline_access`.
- **Resource access denied:** Check app approval, client ID, scopes, and policy. For normal consent instead, call `startAuthorization` for the resource with the same EMA subject and token parameters.

## Next steps

- [Configure Slack with Okta](/docs/connect/ema/slack)
- [Configure Linear with Okta](/docs/connect/ema/linear)
- [Troubleshoot token errors](/docs/connect/concepts/tokens#errors)


---

[View full sitemap](/docs/sitemap)
