Skip to main content

Enterprise-managed authorization

Enterprise-managed authorization (EMA) is an alternative to the OAuth 2.0 authorization code flow, defined as an extension on top of the Model Context Protocol (MCP). It removes the need for your users to individually authorize your app to access the Slack MCP server.

Instead, your app can request special tokens ("ID-JAGs") for a user logged in via an identity provider (IdP) like Okta. These ID-JAGs can be passed into the Slack user token endpoint and exchanged for short-lived tokens with which to access the Slack MCP server. Once the user has logged in to the IdP, this process is transparent to them.

Feature comparison

BehaviorAuthorization code flowEnterprise-managed authorization
Types of tokensBot and user tokens for API and MCP serverUser tokens for MCP server
How authorization is grantedUser reviews scope grants and explicitly authorizes via web formIdP admins define what permissions are granted to users for each MCP server
App installationsWorkspace and organizationOrganization only

Enabling your app for enterprise-managed authorization

Update app configuration

For EMA to work with your app, you need to mark it as supported in the app configuration.

Your app can be marked as supporting EMA via the app settings in the OAuth and Permissions section by toggling it on: enterprise-managed authorization app configuration toggle or by setting the is_enterprise_managed_auth_supported property in the oauth_config section of the manifest to true.


oauth_config:
is_enterprise_managed_auth_supported: true


Getting a user token for the MCP server

Like the authorization code flow, getting a user token via EMA is a three-step process:

  1. Request an identity credential from the IdP.
  2. Exchange the identity credential for an ID-JAG.
  3. Exchange the ID-JAG for an access token.

EMA flow diagram

Request an identity credential from the IdP

You will need to get a credential identifying the user from the IdP. This may either be an OpenID Connect ID token or an OAuth 2.0 refresh token.

If your app supports Single Sign-On (SSO) and a user has signed in using it, you already have a user credential.

If your app does not support SSO, you will need to add support for providing IdP configurations and logging a user in to a configured IdP. The User Authentication section of the EMA specification provides a cursory example of an OpenID Connect exchange. A fuller explanation of how to implement OpenID Connect or SAML 2.0 to get user identity credentials from an IdP is beyond the scope of this doc, but is covered by a number of other guides available online.

Exchange the identity credential for an ID-JAG

You now need to exchange the identity token for an ID-JAG (a token that Slack will trust). This is done by making a call to the token endpoint for the IdP's authorization server. If you don't have the token endpoint URL set explicitly as part of IdP configuration, it may be published at the well-known URL for the authorization server; e.g., https://acme.idp.example/.well-known/oauth-authorization-server

At a minimum, you'll need to provide the following parameters:

ParameterValue
requested_token_typeurn:ietf:params:oauth:token-type:id-jag
audiencehttps://mcp.slack.com/
subject_tokenThe identifying credential you acquired in the previous step
subject_token_typeOne of:
  • urn:ietf:params:oauth:token-type:id_token for an OpenID Connect ID token
  • urn:ietf:params:oauth:token-type:refresh_token for a refresh token
grant_typeurn:ietf:params:oauth:grant-type:token-exchange
  • You will likely want to provide a scope parameter as well with a space-separated set of scopes that the user is requesting.
  • You will need to provide parameters that identify your app to the IdP; e.g., a client_id/client_secret pair or a client_assertion JWT signed with your private key.

An example request looks like this:

POST /oauth2/token HTTP/1.1
Host: acme.idp.example
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&requested_token_type=urn:ietf:params:oauth:token-type:id-jag
&audience=https://mcp.slack.com/
&scope=chat:write+reactions:read
&subject_token=eyJraWQiOiJzMTZ0cVNtODhwREo4VGZCXzdrSEtQ...
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&client_id=2ec954a1d60620116d36d9ceb7
&client_secret=a26d84873504215a34a86d52ef5cd64f4b76

If the request succeeds, you will get a JSON response. The access_token field contains the ID-JAG. Please do not modify the ID-JAG; Slack will not accept it if it has been altered.

Sample response:

{
"issued_token_type": "urn:ietf:params:oauth:token-type:id-jag",
"access_token": "eyJhbGciOiJIUzI1NiIsI...",
"token_type": "N_A",
"scope": "chat:write",
"expires_in": 300
}
Differing scopes

The scopes on the ID-JAG may not be the ones requested. This is subject to the policies configured at the IdP.

Exchange the ID-JAG for an access token

The last step is the same as in the OAuth 2.0 authorization user-centric code flow: call the oauth.v2.user.access endpoint. The difference here is that instead of providing a code parameter and an authorization_code grant type, we provide the ID-JAG as the assertion parameter and a urn:ietf:params:oauth:grant-type:jwt-bearer grant type. For example:

POST /api/oauth.v2.user.access HTTP/1.1
Host: slack.com

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=eyJhbGciOiJIUzI1NiIsI…
&client_id=1234567890.2345678901
&client_secret=foobarbaz…

If this succeeds, the endpoint will return a JSON response with the access token in the access_token property and the issued scopes in the scope property.

Example:

{
"token_type": "Bearer",
"access_token": "xoxp-foo-bar-…",
"expires_in": 3600,
"scope": "chat:write"
}
EMA tokens are short-lived

The access tokens retrieved via EMA are short-lived (an hour at the time of writing, though that may change). The identity credential obtained for the user earlier (the OpenID Connect ID token or refresh token) is long-lived. You will need to add support for retrieving new access tokens using the identity credential frequently.

More information