59°F

Aaron Parecki

  • Articles
  • Notes
  • Photos
  • Enterprise-Ready MCP

    May 12, 2025

    I've seen a lot of complaints about how MCP isn't ready for the enterprise.

    I agree, although maybe not for the reasons you think. But don't worry, this isn't just a rant! I believe we can fix it!

    The good news is the recent updates to the MCP authorization spec that separate out the role of the authorization server from the MCP server have now put the building blocks in place to make this a lot easier.

    But let's back up and talk about what enterprise buyers expect when they are evaluating AI tools to bring into their companies.

    Single Sign-On

    At a minimum, an enterprise admin expects to be able to put an application under their single sign-on system. This enables the company to manage which users are allowed to use which applications, and prevents their users from needing to have their own passwords at the applications. The goal is to get every application managed under their single sign-on (SSO) system. Many large companies have more than 200 applications, so having them all managed through their SSO solution is a lot better than employees having to manage 200 passwords for each application!

    There's a lot more than SSO too, like lifecycle management, entitlements, and logout. We're tackling these in the IPSIE working group in the OpenID Foundation. But for the purposes of this discussion, let's stick to the basics of SSO.

    So what does this have to do with MCP?

    An AI agent using MCP is just another application enterprises expect to be able to integrate into their single-sign-on (SSO) system. Let's take the example of Claude. When rolled out at a company, ideally every employee would log in to their company Claude account using the company identity provider (IdP). This lets the enterprise admin decide how many Claude licenses to purchase and who should be able to use it.

    Connecting to External Apps

    The next thing that should happen after a user logs in to Claude via SSO is they need to connect Claude to their other enterprise apps. This includes the built-in integrations in Claude like Google Calendar and Google Drive, as well as any MCP servers exposed by other apps in use within the enterprise. That could cover other SaaS apps like Zoom, Atlassian, and Slack, as well as home-grown internal apps.

    Today, this process involves a somewhat cumbersome series of steps each individual employee must take. Here's an example of what the user needs to do to connect their AI agent to external apps:

    First, the user logs in to Claude using SSO. This involves a redirect from Claude to the enterprise IdP where they authenticate with one or more factors, and then are redirected back.

    SSO Log in to Claude

    Next, they need to connect the external app from within Claude. Claude provides a button to initiate the connection. This takes the user to that app (in this example, Google), which redirects them to the IdP to authenticate again, eventually getting redirected back to the app where an OAuth consent prompt is displayed asking the user to approve access, and finally the user is redirected back to Claude and the connection is established.

    Connect Google

    The user has to repeat these steps for every MCP server that they want to connect to Claude. There are two main problems with this:

    • This user experience is not great. That's a lot of clicking that the user has to do.
    • The enterprise admin has no visibility or control over the connection established between the two applications.

    Both of these are significant problems. If you have even just 10 MCP servers rolled out in the enterprise, you're asking users to click through 10 SSO and OAuth prompts to establish the connections, and it will only get worse as MCP is more widely adopted within apps. But also, should we really be asking the user if it's okay for Claude to access their data in Google Drive? In a company context, that's not actually the user's decision. That decision should be made by the enterprise IT admin.

    In "An Open Letter to Third-party Suppliers", Patrick Opet, Chief Information Security Officer of JPMorgan Chase writes:

    "Modern integration patterns, however, dismantle these essential boundaries, relying heavily on modern identity protocols (e.g., OAuth) to create direct, often unchecked interactions between third-party services and firms' sensitive internal resources."

    Right now, these app-to-app connections are happening behind the back of the IdP. What we need is a way to move the connections between the applications into the IdP where they can be managed by the enterprise admin.

    Let's see how this works if we leverage a new (in-progress) OAuth extension called "Identity and Authorization Chaining Across Domains", which I'll refer to as "Cross-App Access" for short, enabling the enterprise IdP to sit in the middle of the OAuth exchange between the two apps.

    A Brief Intro to Cross-App Access

    In this example, we'll use Claude as the application that is trying to connect to Slack's (hypothetical) MCP server. We'll start with a high-level overview of the flow, and later go over the detailed protocol.

    First, the user logs in to Claude through the IdP as normal. This results in Claude getting either an ID token or SAML assertion from the IdP, which tells Claude who the user is. (This works the same for SAML assertions or ID tokens, so I'll use ID tokens in the example from here out.) This is no different than what the user would do today when signing in to Claude.

    Step 1 and 2 SSO

    Then, instead of prompting the user to connect Slack, Claude takes the ID token back to the IdP in a request that says "Claude is requesting access to this user's Slack account."

    The IdP validates the ID token, sees it was issued to Claude, and verifies that the admin has allowed Claude to access Slack on behalf of the given user. Assuming everything checks out, the IdP issues a new token back to Claude.

    Step 3 and 4 Cross-Domain Request

    Claude takes the intermediate token from the IdP to Slack saying "hi, I would like an access token for the Slack MCP server. The IdP gave me this token with the details of the user to issue the access token for." Slack validates the token the same way it would have validated an ID token. (Remember, Slack is already configured for SSO to the IdP for this customer as well, so it already has a way to validate these tokens.) Slack is able to issue an access token giving Claude access to this user's resources in its MCP server.

    Step 5-7 Access Token Request

    This solves the two big problems:

    • The exchange happens entirely without any user interaction, so the user never sees any prompts or any OAuth consent screens.
    • Since the IdP sits in between the exchange, this gives the enterprise admin a chance to configure the policies around which applications are allowed this direct connection.

    The other nice side effect of this is since there is no user interaction required, the first time a new user logs in to Claude, all their enterprise apps will be automatically connected without them having to click any buttons!

    Cross-App Access Protocol

    Now let's look at what this looks like in the actual protocol. This is based on the adopted in-progress OAuth specification "Identity and Authorization Chaining Across Domains". This spec is actually a combination of two RFCs: Token Exchange (RFC 8693), and JWT Profile for Authorization Grants (RFC 7523). Both RFCs as well as the "Identity and Authorization Chaining Across Domains" spec are very flexible. While this means it is possible to apply this to many different use cases, it does mean we need to be a bit more specific in how to use it for this use case. For that purpose, I've written a profile of the Identity Chaining draft called "Identity Assertion Authorization Grant" to fill in the missing pieces for the specific use case detailed here.

    Let's go through it step by step. For this example we'll use the following entities:

    • Claude - the "Requesting Application", which is attempting to access Slack
    • Slack - the "Resource Application", which has the resources being accessed through MCP
    • Okta - the enterprise identity provider which users at the example company can use to sign in to both apps

    Cross-App Access Diagram

    Single Sign-On

    First, Claude gets the user to sign in using a standard OpenID Connect (or SAML) flow in order to obtain an ID token. There isn't anything unique to this spec regarding this first stage, so I will skip the details of the OpenID Connect flow and we'll start with the ID token as the input to the next step.

    Token Exchange

    Claude, the requesting application, then makes a Token Exchange request (RFC 8693) to the IdP's token endpoint with the following parameters:

    • requested_token_type: The value urn:ietf:params:oauth:token-type:id-jag indicates that an ID Assertion JWT is being requested.
    • resource: The Issuer URL of the Resource Application's authorization server.
    • subject_token: The identity assertion (e.g. the OpenID Connect ID Token or SAML assertion) for the target end-user.
    • subject_token_type: Either urn:ietf:params:oauth:token-type:id_token or urn:ietf:params:oauth:token-type:saml2 as defined by RFC 8693.

    This request will also include the client credentials that Claude would use in a traditional OAuth token request, which could be a client secret or a JWT Bearer Assertion.

    POST /oauth2/token HTTP/1.1
    Host: acme.okta.com
    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
    &resource=https://mcp.slack.com/
    &subject_token=eyJraWQiOiJzMTZ0cVNtODhwREo4VGZCXzdrSEtQ...
    &subject_token_type=urn:ietf:params:oauth:token-type:id_token
    &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
    &client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0...
    

    ID Assertion Validation and Policy Evaluation

    At this point, the IdP evaluates the request and decides whether to issue the requested "ID Assertion JWT". The request will be evaluated based on the validity of the arguments, as well as the configured policy by the customer.

    For example, the IdP validates that the ID token in this request was issued to the same client that matches the provided client authentication. It evaluates that the user still exists and is active, and that the user is assigned the Resource Application. Other policies can be evaluated at the discretion of the IdP, just like it can during a single sign-on flow.

    If the IdP agrees that the requesting app should be authorized to access the given user's data in the resource app's MCP server, it will respond with a Token Exchange response to issue the token:

    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: no-store
    
    {
      "issued_token_type": "urn:ietf:params:oauth:token-type:id-jag",
      "access_token": "eyJhbGciOiJIUzI1NiIsI...",
      "token_type": "N_A",
      "expires_in": 300
    }
    

    The claims in the issued JWT are defined in "Identity Assertion Authorization Grant". The JWT is signed using the same key that the IdP signs ID tokens with. This is a critical aspect that makes this work, since again we assumed that both apps would already be configured for SSO to the IdP so would already be aware of the signing key for that purpose.

    At this point, Claude is ready to request a token for the Resource App's MCP server

    Access Token Request

    The JWT received in the previous request can now be used as a "JWT Authorization Grant" as described by RFC 7523. To do this, Claude makes a request to the MCP authorization server's token endpoint with the following parameters:

    • grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer
    • assertion: The Identity Assertion Authorization Grant JWT obtained in the previous token exchange step

    For example:

    POST /oauth2/token HTTP/1.1
    Host: auth.slack.com
    Authorization: Basic yZS1yYW5kb20tc2VjcmV0v3JOkF0XG5Qx2
    
    grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
    assertion=eyJhbGciOiJIUzI1NiIsI...
    

    Slack's authorization server can now evaluate this request to determine whether to issue an access token. The authorization server can validate the JWT by checking the issuer (iss) in the JWT to determine which enterprise IdP the token is from, and then check the signature using the public key discovered at that server. There are other claims to be validated as well, described in Section 6.1 of the Identity Assertion Authorization Grant.

    Assuming all the validations pass, Slack is ready to issue an access token to Claude in the token response:

    HTTP/1.1 200 OK
    Content-Type: application/json
    Cache-Control: no-store
    
    {
      "token_type": "Bearer",
      "access_token": "2YotnFZFEjr1zCsicMWpAA",
      "expires_in": 86400,
      "refresh_token": "tGzv3JOkF0XG5Qx2TlKWIA",
    }
    

    This token response is the same format that Slack's authorization server would be responding to a traditional OAuth flow. That's another key aspect of this design that makes it scalable. We don't need the resource app to use any particular access token format, since only that server is responsible for validating those tokens.

    Now that Claude has the access token, it can make a request to the (hypothetical) Slack MCP server using the bearer token the same way it would have if it got the token using the traditional redirect-based OAuth flow.

    Cross-App Access Sequence Diagram

    Here's the flow again, this time as a sequence diagram.

    Cross-App Access Sequence Diagram

    1. The client initiates a login request
    2. The user's browser is redirected to the IdP
    3. The user logs in at the IdP
    4. The IdP returns an OAuth authorizaiton code to the user's browser
    5. The user's browser delivers the authorization code to the client
    6. The client exchanges the authorization code for an ID token at the IdP
    7. The IdP returns an ID token to the client

    At this point, the user is logged in to the MCP client. Everything up until this point has been a standard OpenID Connect flow.

    1. The client makes a direct Token Exchange request to the IdP to exchange the ID token for a cross-domain "ID Assertion JWT"
    2. The IdP validates the request and checks the internal policy
    3. The IdP returns the ID-JAG to the client
    4. The client makes a token request using the ID-JAG to the MCP authorization server
    5. The authorization server validates the token using the signing key it also uses for its OpenID Connect flow with the IdP
    6. The authorization server returns an access token
    7. The client makes a request with the access token to the MCP server
    8. The MCP server returns the response

    For a more detailed step by step of the flow, see Appendix A.3 of the Identity Assertion Authorization Grant.

    Next Steps

    If this is something you're interested in, we'd love your help! The in-progress spec is publicly available, and we're looking for people interested in helping prototype it. If you're building an MCP server and you want to make it enterprise-ready, I'd be happy to help you build this!

    You can find me at a few related events coming up:

    • MCP Night on May 14
    • MCP Developers Summit on May 23
    • AWS MCP Agents Hackathon on May 30
    • Identiverse 2025 on June 3-6

    And of course you can always find me on LinkedIn or email me at aaron.parecki@okta.com.

    Burlingame, California • 57°F
    Mon, May 12, 2025 10:01pm -07:00 #mcp #oauth
    2 likes
    • Jamie Tanna
    • Jamie Tanna
Posted in /articles using quill.p3k.io

Hi, I'm Aaron Parecki, Director of Identity Standards at Okta, and co-founder of IndieWebCamp. I maintain oauth.net, write and consult about OAuth, and participate in the OAuth Working Group at the IETF. I also help people learn about video production and livestreaming. (detailed bio)

I've been tracking my location since 2008 and I wrote 100 songs in 100 days. I've spoken at conferences around the world about owning your data, OAuth, quantified self, and explained why R is a vowel. Read more.

  • Director of Identity Standards at Okta
  • IndieWebCamp Founder
  • OAuth WG Editor
  • OpenID Board Member

  • 🎥 YouTube Tutorials and Reviews
  • 🏠 We're building a triplex!
  • ⭐️ Life Stack
  • ⚙️ Home Automation
  • All
  • Articles
  • Bookmarks
  • Notes
  • Photos
  • Replies
  • Reviews
  • Trips
  • Videos
  • Contact
© 1999-2025 by Aaron Parecki. Powered by p3k. This site supports Webmention.
Except where otherwise noted, text content on this site is licensed under a Creative Commons Attribution 3.0 License.
IndieWebCamp Microformats Webmention W3C HTML5 Creative Commons
WeChat ID
aaronpk_tv