diff --git a/auth4genai/mcp/get-started/call-your-apis-on-users-behalf.mdx b/auth4genai/mcp/get-started/call-your-apis-on-users-behalf.mdx index 3063b45391..d14ed6943d 100644 --- a/auth4genai/mcp/get-started/call-your-apis-on-users-behalf.mdx +++ b/auth4genai/mcp/get-started/call-your-apis-on-users-behalf.mdx @@ -25,9 +25,9 @@ import RunMcpServerPython from "/snippets/mcp/get-started/call-your-apis/run-mcp import ExchangeAccessTokenJs from "/snippets/mcp/get-started/call-your-apis/exchange-access-token-js.mdx"; import ExchangeAccessTokenPython from "/snippets/mcp/get-started/call-your-apis/exchange-access-token-python.mdx"; -To call your APIs on behalf of your users, the MCP server needs to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API. +Auth0 implements the On-Behalf-Of (OBO) token exchange ([RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html)) to enable MCP servers to preserve user identity and permissions when calling downstream APIs. In the OBO token exchange flow, the MCP server acts in a dual role: as a resource server when receiving requests from the client, and as a client when calling your downstream API on the user’s behalf. -In Auth0, this is called Custom Token Exchange and uses [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html). This flow involves the MCP server acting as both a resource server (for the client) and a client (for the upstream API). +To call your APIs on a user’s behalf, the MCP server must exchange the Auth0 access token it receives from the MCP client for a new access token with a different audience. The original token has the MCP server as its audience, while the new token has your downstream API as its audience. -By the end of this quickstart, you should have an MCP server that can: + +When you purchase the Auth0 for AI Agents add-on, you can use your subscription tier’s maximum Authentication API rate limit for OBO token exchanges. For example, if you are on [Private Cloud 100 RPS](https://auth0.com/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy/rate-limit-configurations/tier-100-rps-private-cloud), you can exceed the OBO token exchange rate limit of 30 RPS and leverage the full 100 RPS capacity for your OBO token exchange requests. The Authentication API limit is shared and acts as the global ceiling for all Authentication API requests, including logins, token refreshes, and token exchanges combined. Reach out to your Technical Account Manager for more information. + -* Exchange an Auth0 access token for another Auth0 access token with a different audience using Custom Token Exchange -* Verify the access token using the `@auth0/auth0-api-js` library (which uses `jose` under the hood) or the `auth0-api-python` library for Python against your tenant JWKS, enforcing issuer, audience, and RS256 +By the end of this quickstart, you will have: + +* An MCP server that calls your protected APIs on behalf of authenticated users using the OBO token exchange +* Token verification and exchange using the `@auth0/auth0-api-js` library (JavaScript) or `auth0-api-python` library (Python) +* A working example demonstrating the end-to-end OBO token exchange flow from client to MCP server to downstream API @@ -57,14 +62,14 @@ By the end of this quickstart, you should have an MCP server that can: -## Set up the Auth0 Applications and APIs +## Set up the Auth0 applications and APIs -When an MCP server needs to call underlying APIs, it needs to perform a Custom Token Exchange to obtain an access token with the audience set to the API rather than the MCP server itself. Because of this architecture, the MCP server acts in a dual role. +The MCP server's dual role requires two configurations in Auth0: -1. To the MCP client (e.g., an IDE, an AI assistant), the MCP server acts as a **Resource Server** (an API). -2. To the underlying API (e.g., your own API), the MCP server acts as a **Client**. +1. As a resource server: An API that the MCP client calls +2. As a client: An application that can exchange tokens to call downstream APIs -Because of this, you will set up the MCP server twice on your Auth0 tenant, both as an API and as a Client. +Let's set up both configurations. ### Create an API to represent your MCP server @@ -72,9 +77,14 @@ Because of this, you will set up the MCP server twice on your Auth0 tenant, both -### Create an Application for your MCP server +### Create an application for your MCP server + +Create an application that allows your MCP server to act as a client for the OBO token exchange. This application must: +- Have the same identifier as your MCP server API (`resource_server_identifier`) +- Use `app_type: resource_server` to link it to the MCP server +- Enable OBO token exchange in the `token_exchange` configuration -In the custom token exchange scenario, the MCP server acts as a client in order to obtain an Auth0 access token with custom token exchange: +The following command creates an application linked to your MCP server with OBO token exchange enabled: ```shell wrap lines auth0 api post clients --data '{ @@ -83,32 +93,44 @@ auth0 api post clients --data '{ "oidc_conformant": true, "resource_server_identifier": "http://localhost:3001/", "token_exchange": { - "allow_any_profile_of_type": ["custom_authentication"] + "allow_any_profile_of_type": ["on_behalf_of_token_exchange"] } }' | jq -c '{client_id, client_secret}' > auth0-app-details.json ``` -The output of the command will be a JSON object with the `client_id` and `client_secret` of the newly created client, which will be used in the next steps to configure the MCP server environment. +Save the `client_id` and `client_secret` from the output; you'll need them to configure your MCP server environment. -And finally, because you need an API to call to, let's create one. +### Create a downstream API -### Create a first-party API to call from the MCP server +Create the protected API that your MCP server will call on the user's behalf. This API represents your business logic API (e.g., a calendar API, document API, etc.). -Since your objective is to have the MCP server make a tool call that calls a protected first-party API you need to configure an API in Auth0, this will be your upstream API: +The following command creates an API with the following configurations: +- Client grants: Gives the MCP server [user-delegated and client access](https://auth0.com/docs/get-started/applications/application-access-to-apis-client-grants) the API +- Scope enforcement: Requires specific permissions (`read:private`) +- First-party status: Skips user consent for your own applications ```shell auth0 api post resource-servers --data '{ "identifier": "http://localhost:8787/", - "name": "Protected First Party API", + "name": "Protected Downstream API", "signing_alg": "RS256", "enforce_policies": true, "scopes": [ {"value": "read:private", "description": "Private scope"} - ] -}' | jq -r '"Audience: " + .identifier' + ], + "subject_type_authorization": { + "user": { + "policy": "require_client_grant" + }, + "client": { + "policy": "require_client_grant" + } + }, + "skip_consent_for_verifiable_first_party_clients": false +}' ``` -Save the `Audience` from the command output; you'll need it in a later step. +Save the API identifier (audience) from the output; you'll configure it in your environment variables as `API_AUTH0_AUDIENCE`. ## Sample app @@ -120,29 +142,33 @@ Save the `Audience` from the command output; you'll need it in a later step. Once downloaded, extract the files and open the project in your preferred IDE. - Clone the repository and navigate to the sample app folder which includes a FastMCP MCP server with an Auth0 integration and a protected API built with + Clone the repository and navigate to the sample app folder, which includes a FastMCP MCP server with an Auth0 integration and a protected API built with [Fastify](https://fastify.dev/). ```shell wrap lines git clone https://github.com/auth0-samples/auth0-ai-samples.git - cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-customtokenexchange-js + cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-on-behalf-of-tokenexchange-js ``` Once cloned, open the project in your preferred IDE. - The sample app demonstrates custom token exchange with a `greet` tool that calls your protected API on behalf of the authenticated user. + The sample app demonstrates the complete OBO flow with a `greet` tool that: + 1. Receives an authenticated request from an MCP client + 2. Exchanges the token for downstream API access + 3. Calls your protected API on the user's behalf + 4. Returns the result ## Install packages - Ensure you have npm installed or follow the instructions to [install npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) in its documentation. In the `fastmcp-mcp-customtokenexchange-js` directory, install the required packages: + Make sure you have [npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). Then, in the `fastmcp-mcp-on-behalf-of-tokenexchange-js` directory, install the required packages: ```shell npm install @@ -151,12 +177,6 @@ Save the `Audience` from the command output; you'll need it in a later step. ## Create your environment file - ## Use Custom Token Exchange Action - - - ## Set up the token exchange profile - - ## Run the MCP server and the API @@ -171,7 +191,7 @@ Save the `Audience` from the command output; you'll need it in a later step. Once downloaded, extract the files and open the project in your preferred IDE. @@ -181,18 +201,22 @@ Save the `Audience` from the command output; you'll need it in a later step. ```shell wrap lines git clone https://github.com/auth0-samples/auth0-ai-samples.git - cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-customtokenexchange-python + cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-on-behalf-of-tokenexchange-python ``` Once cloned, open the project in your preferred IDE. - The sample app demonstrates custom token exchange with a `greet` tool that calls your protected API on behalf of the authenticated user. + The sample app demonstrates the complete OBO flow with a `greet` tool that: + 1. Receives an authenticated request from an MCP client + 2. Exchanges the token for downstream API access + 3. Calls your protected API on the user's behalf + 4. Returns the result ## Install packages - Ensure you have poetry installed or follow the instructions to [install poetry](https://python-poetry.org/docs/) in its documentation. In the `fastmcp-mcp-customtokenexchange-python` directory, install the required packages: + Make sure you have [poetry installed](https://python-poetry.org/docs/). Then, in the `fastmcp-mcp-on-behalf-of-tokenexchange-python` directory, install the required packages: ```shell poetry install @@ -201,12 +225,6 @@ Save the `Audience` from the command output; you'll need it in a later step. ## Create your environment file - ## Use Custom Token Exchange Action - - - ## Set up the token exchange profile - - ## Run the MCP server and the API diff --git a/auth4genai/snippets/mcp/get-started/call-your-apis/create-env-file.mdx b/auth4genai/snippets/mcp/get-started/call-your-apis/create-env-file.mdx index 890c04dd78..a888f34cd7 100644 --- a/auth4genai/snippets/mcp/get-started/call-your-apis/create-env-file.mdx +++ b/auth4genai/snippets/mcp/get-started/call-your-apis/create-env-file.mdx @@ -11,10 +11,9 @@ CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) \ && echo "MCP_SERVER_URL=http://localhost:3001/" >> .env \ && echo "MCP_AUTH0_CLIENT_ID=${CLIENT_ID}" >> .env \ && echo "MCP_AUTH0_CLIENT_SECRET=${CLIENT_SECRET}" >> .env \ -&& echo "MCP_AUTH0_SUBJECT_TOKEN_TYPE=urn:fastmcp:mcp" >> .env \ -&& echo "MCP_AUTH0_EXCHANGE_SCOPE=openid offline_access read:private" >> .env \ +&& echo "MCP_AUTH0_EXCHANGE_SCOPE=read:private" >> .env \ && echo "API_AUTH0_AUDIENCE=http://localhost:8787/" >> .env \ -&& echo "API_BASE_URL=http://localhost:8787/" >> .env \ +&& echo "API_BASE_URL=http://localhost:8787" >> .env \ && rm auth0-app-details.json \ && echo ".env file created with your Auth0 details:" \ && cat .env diff --git a/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-js.mdx b/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-js.mdx index 901ea51677..b55962bc77 100644 --- a/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-js.mdx +++ b/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-js.mdx @@ -1,17 +1,19 @@ -To call your APIs on behalf of your users, the MCP server needs to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API. In Auth0, this is called Custom Token Exchange and uses [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html). +To call your APIs on a user's behalf, the MCP server uses the OBO token exchange to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API. -### The Orchestrator: `bearerForUpstream` +### Token exchange implementation -The process begins with the `bearerForUpstream` function. Its main job is to take the initial token (the `subjectToken`), manage the exchange process, and handle any potential errors gracefully. +The MCP server exchanges tokens in two steps: -This function serves as a safe wrapper around our exchange logic. +#### 1. Wrapper function: `bearerForUpstream()` + +This function safely handles the token exchange process and error handling: ```javascript wrap lines highlight={5} -async function bearerForUpstream(subjectToken: string) { - if (!subjectToken) return { token: null, scopes: null }; +async function bearerForUpstream(accessToken: string) { + if (!accessToken) return { token: null, scopes: null }; try { - const result = await exchangeCustomToken(subjectToken); + const result = await exchangeTokenOnBehalfOf(accessToken); return { token: result.accessToken, scopes: result.scope, @@ -23,28 +25,30 @@ async function bearerForUpstream(subjectToken: string) { } ``` -As you can see, it calls `exchangeCustomToken` and, on a successful exchange, returns the new `accessToken` and its associated scope. If the exchange fails, it logs the error and re-throws it to be handled upstream. +`bearerForUpstream()` calls `exchangeTokenOnBehalfOf()` and, upon a successful exchange, returns the new `accessToken` and its associated scope. If the exchange fails, it logs the error and re-throws it to be handled upstream. -### The core logic: `exchangeCustomToken` +#### 2. Exchange function: `exchangeTokenOnBehalfOf()` -This function, located in `src/auth0.ts`, contains the actual token exchange logic. It uses the `ApiClient` from the `auth0-api-js` SDK to simplify the interaction with Auth0's `/oauth/token` endpoint. +This function, located in `src/auth0.ts`, calls Auth0's OBO token exchange endpoint using the SDK. -First, we initialize the `ApiClient` with the credentials of the application performing the exchange: +First, initialize the `ApiClient` with the credentials of the MCP server application: ```javascript wrap lines - const exchangeClient = new ApiClient({ +const apiClient = new ApiClient({ domain: AUTH0_DOMAIN, - audience: API_AUTH0_AUDIENCE, + audience: AUTH0_AUDIENCE, clientId: MCP_AUTH0_CLIENT_ID, clientSecret: MCP_AUTH0_CLIENT_SECRET, }); ``` -With the client configured, the `exchangeCustomToken` function uses the client's `getTokenByExchangeProfile` method to perform the token exchange. This method implements the [Custom Token Exchange](https://auth0.com/docs/authenticate/custom-token-exchange) flow. + +Once you've configured the MCP server application, the `exchangeTokenOnBehalfOf()` function uses the client's `getTokenOnBehalfOf()` method to perform the token exchange. + +`getTokenOnBehalfOf()` implements the OBO token exchange flow, which allows the MCP server to obtain a new token to call the downstream API while preserving the user's identity. It takes in the `accessToken`, `audience`, and `scope` parameters. ```javascript wrap lines -export async function exchangeCustomToken(subjectToken: string) { - return await exchangeClient.getTokenByExchangeProfile(subjectToken, { - subjectTokenType: MCP_AUTH0_SUBJECT_TOKEN_TYPE, +export async function exchangeTokenOnBehalfOf(accessToken: string) { + return await apiClient.getTokenOnBehalfOf(accessToken, { audience: API_AUTH0_AUDIENCE, ...(MCP_AUTH0_EXCHANGE_SCOPE && { scope: MCP_AUTH0_EXCHANGE_SCOPE }), }); diff --git a/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-python.mdx b/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-python.mdx index 3a8b14b057..4775c38ce7 100644 --- a/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-python.mdx +++ b/auth4genai/snippets/mcp/get-started/call-your-apis/exchange-access-token-python.mdx @@ -1,9 +1,9 @@ -To call your APIs on behalf of your users, the MCP server needs to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API. In Auth0, this is called Custom Token Exchange and uses [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html). +To call your APIs on a user's behalf, the MCP server uses the OBO token exchange to exchange the Auth0 access token it received from the MCP client (with the audience set to the MCP server itself) for a new Auth0 access token with the audience set to your API. ### How tools use token exchange -Here's how the `greet` tool performs token exchange and calls the upstream API: +Here's how the `greet` tool performs the token exchange and calls the upstream API: -```python wrap lines highlight={10,11,12,13,14,19} +```python wrap lines highlight={10,11,12,13,14,17} @mcp.tool(name="greet") @require_scopes(["tool:greet"]) async def greet(name: str, ctx: Context) -> str: @@ -14,7 +14,7 @@ async def greet(name: str, ctx: Context) -> str: logger.info(f"Greet tool invoked for user: {user_id}") # Exchange token and call upstream API - exchange_result = await exchange_custom_token( + exchange_result = await exchange_token_on_behalf_of( ctx.request_context.request.state.api_client, auth_info["token"] ) @@ -29,32 +29,31 @@ async def greet(name: str, ctx: Context) -> str: return f"Hello, {user_name} ({user_id})!\nUpstream API Response: {json.dumps(upstream_result, indent=2)}" ``` -### The core logic: `exchange_custom_token` +### The core logic: `exchange_token_on_behalf_of()` -The Python implementation uses the `exchange_custom_token` function that handles the token exchange process. +The Python implementation uses the `exchange_token_on_behalf_of()` function that handles the token exchange: -```python wrap lines highlight={3,4,5,6,7,8} -async def exchange_custom_token(api_client, subject_token: str) -> dict: - """Exchange subject token for access token via Custom Token Exchange.""" - result = await api_client.get_token_by_exchange_profile( - subject_token=subject_token, - subject_token_type=config.mcp_auth0_subject_token_type, +```python wrap lines highlight={3,4,5,6} +async def exchange_token_on_behalf_of(api_client, access_token: str) -> dict: + """Exchange access token for downstream API token via On-Behalf-Of Token Exchange.""" + result = await api_client.get_token_on_behalf_of( + access_token=access_token, audience=config.api_auth0_audience, scope=config.mcp_auth0_exchange_scope or None ) return {"token": result["access_token"], "scopes": result.get("scope", "")} ``` -This function uses the `get_token_by_exchange_profile` method of `ApiClient` from the `auth0-api-python` SDK and, on a successful exchange, returns the new access token and its associated scopes. This method implements the [Custom Token Exchange](https://auth0.com/docs/authenticate/custom-token-exchange) flow. +`exchange_token_on_behalf_of()` uses the `get_token_on_behalf_of()` method of `ApiClient` from the `auth0-api-python` SDK. It takes in the `access_token`, `audience`, and optional `scope` parameters, and upon a successful exchange, returns the new access token and its associated scopes. -### Client Configuration +### Client configuration -The `ApiClient` is initialized in the `Auth0Middleware` (located in `src/auth0/middleware.py`) with the credentials of the application performing the exchange: +The `ApiClient` is initialized in the `Auth0Middleware` (located in `src/auth0/middleware.py`) with the credentials of the MCP server application: ```python src/auth0/middleware.py wrap lines self.client = ApiClient(ApiClientOptions( domain=domain, # AUTH0_DOMAIN env var - audience=audience, # API_AUTH0_AUDIENCE env var + audience=audience, # AUTH0_AUDIENCE env var client_id=client_id, # MCP_AUTH0_CLIENT_ID env var client_secret=client_secret # MCP_AUTH0_CLIENT_SECRET env var )) diff --git a/auth4genai/snippets/mcp/get-started/pre-reqs/enable-resource-param-step.mdx b/auth4genai/snippets/mcp/get-started/pre-reqs/enable-resource-param-step.mdx index 30592d82e0..804e4d47a5 100644 --- a/auth4genai/snippets/mcp/get-started/pre-reqs/enable-resource-param-step.mdx +++ b/auth4genai/snippets/mcp/get-started/pre-reqs/enable-resource-param-step.mdx @@ -1,7 +1,7 @@ -To use the `resource` parameter in your access tokens, you need to enable the compatibility profile. +To use the `resource` parameter in your access tokens, you need to enable the Resource Parameter Compatibility Profile. The quickest way to enable it is through the [Auth0 Dashboard](https://manage.auth0.com/dashboard/): 1. Navigate to **Settings** on the left sidebar. -2. Click on [**Advanced**](https://manage.auth0.com/dashboard/#/tenant/advanced) in the top right corner. -3. Scroll down to the Settings section, find and enable the **Resource Parameter Compatibility Profile** toggle. \ No newline at end of file +2. Select [**Advanced**](https://manage.auth0.com/dashboard/#/tenant/advanced) in the top right corner. +3. Scroll down to the **Settings** section and enable the **Resource Parameter Compatibility Profile** toggle. \ No newline at end of file diff --git a/auth4genai/snippets/mcp/get-started/pre-reqs/prerequisites.mdx b/auth4genai/snippets/mcp/get-started/pre-reqs/prerequisites.mdx index 1c601f430e..15ea911ffa 100644 --- a/auth4genai/snippets/mcp/get-started/pre-reqs/prerequisites.mdx +++ b/auth4genai/snippets/mcp/get-started/pre-reqs/prerequisites.mdx @@ -3,10 +3,6 @@ import MCPGetStartedAuth0CLIStep from "/snippets/mcp/get-started/pre-reqs/auth0- ## Prerequisites - -Auth for MCP is currently available in Early Access. To join the Early Access program, please complete [this form](https://forms.gle/hvJ1ZRLmHr9YjV2a9). We'll reach out to you when your request is processed. - - To continue with this quickstart, you need to have an [Auth0 account](https://auth0.com/signup).