Enterprise-Managed Authorization (EMA) Beta
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 to getToken. Edgenivo 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.
EMA is a Model Context Protocol (MCP) authorization extension. 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:
- Edgenivo Connect: Enable Enterprise-Managed Authorization (EMA) on the IdP and resource connectors.
- Slack: Enable Enterprise-Managed Authorization for access to the Slack MCP server.
- Okta: Configure Cross App Access (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 or Linear setup guide.
- Configure the SDK and project authentication using the quickstart.
- Use an IdP and resource service that support EMA's ID-JAG token exchange, with administrator-approved access policies.
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:
- Register and activate an AI agent linked to your OIDC application.
- Enable XAA on each resource application and connect it to the agent. Allow only the required scopes.
- Use the org authorization server, not a custom server such as
/oauth2/default. See Okta's token exchange reference.
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.
Configure Passport for your project with the EMA-enabled IdP connector. Include openid and offline_access in the connector's User Authorization scopes so Connect can store an IdP refresh token. Start a new Passport session after changing these settings.
Install the SDKs in your app:
pnpm add @vercel/connect @vercel/passportIn 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:
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. The local development identity has no token, so requireIdentity() throws PassportIdentityError instead of using it for EMA.
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.
Use the user authorization flow 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.
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:
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.
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, requestingopenidandoffline_access. - Resource access denied: Check app approval, client ID, scopes, and policy. For normal consent instead, call
startAuthorizationfor the resource with the same EMA subject and token parameters.
Was this helpful?