Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 27 additions & 25 deletions auth4genai/mcp/get-started/call-your-apis-on-users-behalf.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ permalink: "/mcp/get-started/call-your-apis-on-users-behalf"
---

import MCPGetStartedPrerequisites from "/snippets/mcp/get-started/pre-reqs/prerequisites.mdx";
import MCPGetStartedEnableOBOTokenExchangeStep from "/snippets/mcp/get-started/pre-reqs/enable-obo-token-exchange.mdx";
import MCPGetStartedConfigTenantSettings from "/snippets/mcp/get-started/config-tenant/config-tenant-settings.mdx";
import MCPGetStartedCreateRoles from "/snippets/mcp/get-started/config-tenant/roles-management.mdx";
import CreateProfile from "/snippets/mcp/get-started/create-custom-token-exchange-profile.mdx";
Expand All @@ -25,9 +26,13 @@ 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";

<Note>
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.
</Note>

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). This flow involves the MCP server acting as both a resource server (for the client) and a client (for the upstream API).
In Auth0, this is called On-Behalf-Of Token Exchange (OBO) 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).

<Frame>
<img
Expand All @@ -44,7 +49,7 @@ In Auth0, this is called Custom Token Exchange and uses [RFC 8693](https://www.r

By the end of this quickstart, you should have an MCP server that can:

* Exchange an Auth0 access token for another Auth0 access token with a different audience using Custom Token Exchange
* Exchange an Auth0 access token for another Auth0 access token with a different audience using On-Behalf-Of 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

<MCPGetStartedPrerequisites />
Expand All @@ -59,7 +64,7 @@ By the end of this quickstart, you should have an MCP server that can:

## 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.
When an MCP server needs to call underlying APIs, it needs to perform an On-Behalf-Of 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.

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**.
Expand All @@ -74,7 +79,7 @@ Because of this, you will set up the MCP server twice on your Auth0 tenant, both

### Create an Application for your MCP server

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 MCP server acts as a client in order to obtain an Auth0 access token with On-Behalf-Of token exchange:

```shell wrap lines
auth0 api post clients --data '{
Expand Down Expand Up @@ -108,7 +113,16 @@ auth0 api post resource-servers --data '{
}' | jq -r '"Audience: " + .identifier'
```

Save the `Audience` from the command output; you'll need it in a later step.
Save the `Audience` from the command output; you'll need it in a later step. Make sure you enable **Allow Skipping User Consent** for the API in the Auth0 Dashboard.

### Create client grant

You need to create a [user-delegated client grant](https://auth0.com/docs/get-started/applications/application-access-to-apis-client-grants) between the Custom API client and the downstream API to authorize access.

1. Navigate to **Applications > Applications** and select your Custom API client.
2. Under **APIs**, find your resource server (i.e., `https://my-api.example.com`) and select **Edit**.
3. Under **User-Delegated Access**, select **Authorized**, then select **Specific Permissions** with the permissions you want to grant or **All**.
4. Select **Save**.

## Sample app
<Tabs>
Expand All @@ -120,7 +134,7 @@ Save the `Audience` from the command output; you'll need it in a later step.

<DownloadQuickstartButton
category="auth-for-mcp"
framework="fastmcp-mcp-customtokenexchange-js"
framework="fastmcp-mcp-on-behalf-of-tokenexchange-js"
/>

Once downloaded, extract the files and open the project in your preferred IDE.
Expand All @@ -131,18 +145,18 @@ 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-js
cd auth0-ai-samples/auth-for-mcp/fastmcp-mcp-on-behalf-of-tokenexchange-js
```

Once cloned, open the project in your preferred IDE.
</Tab>
</Tabs>

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 on-behalf-of token exchange with a `greet` tool that calls your protected API on behalf of the authenticated user.

## 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:
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-on-behalf-of-tokenexchange-js` directory, install the required packages:

```shell
npm install
Expand All @@ -151,12 +165,6 @@ Save the `Audience` from the command output; you'll need it in a later step.
## Create your environment file
<CreateEnvFile/>

## Use Custom Token Exchange Action
<CustomTokenExchangeAction/>

## Set up the token exchange profile
<CreateProfile />

## Run the MCP server and the API
<RunMcpServerJs/>

Expand All @@ -171,7 +179,7 @@ Save the `Audience` from the command output; you'll need it in a later step.

<DownloadQuickstartButton
category="auth-for-mcp"
framework="fastmcp-mcp-customtokenexchange-python"
framework="fastmcp-mcp-on-behalf-of-tokenexchange-python"
/>

Once downloaded, extract the files and open the project in your preferred IDE.
Expand All @@ -181,18 +189,18 @@ 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.
</Tab>
</Tabs>

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 on-behalf-of token exchange with a `greet` tool that calls your protected API on behalf of the authenticated user.

## 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:
Ensure you have poetry installed or follow the instructions to [install poetry](https://python-poetry.org/docs/) in its documentation. In the `fastmcp-mcp-on-behalf-of-tokenexchange-python` directory, install the required packages:

```shell
poetry install
Expand All @@ -201,12 +209,6 @@ Save the `Audience` from the command output; you'll need it in a later step.
## Create your environment file
<CreateEnvFile/>

## Use Custom Token Exchange Action
<CustomTokenExchangeAction/>

## Set up the token exchange profile
<CreateProfile />

## Run the MCP server and the API
<RunMcpServerPython/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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 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 On-Behalf-Of Token Exchange and uses [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html).

### The Orchestrator: `bearerForUpstream`

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 process begins with the `bearerForUpstream` function. Its main job is to take the initial access token, manage the exchange process, and handle any potential errors gracefully.

This function serves as a safe wrapper around our exchange logic.

```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,
Expand All @@ -23,30 +23,32 @@ 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.
As you can see, it calls `exchangeTokenOnBehalfOf` 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.

### The core logic: `exchangeCustomToken`
### The core logic: `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`, contains the actual token exchange logic. It uses the `ApiClient` from the `@auth0/auth0-api-js` SDK to simplify the interaction with Auth0's `/oauth/token` endpoint.

First, we initialize the `ApiClient` with the credentials of the application performing the exchange:
First, we 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.

With the client configured, the `exchangeTokenOnBehalfOf` function uses the client's `getTokenOnBehalfOf` method to perform the token exchange. This method implements the On-Behalf-Of Token Exchange flow, which allows the MCP server to obtain a new token for calling the downstream API while preserving the user's identity.

```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 }),
});
}
```

The key difference from custom token exchange is that this uses the `getTokenOnBehalfOf` method with just the `audience` and optional `scope` parameters, making it simpler and more straightforward for the on-behalf-of use case.
Original file line number Diff line number Diff line change
@@ -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 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 On-Behalf-Of Token Exchange and uses [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693.html).

### How tools use token exchange
Here's how the `greet` tool performs 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:
Expand All @@ -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"]
)
Expand All @@ -29,32 +29,33 @@ 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 process.

```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.
This function uses the `get_token_on_behalf_of` 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 On-Behalf-Of Token Exchange flow, which allows the MCP server to obtain a new token for calling the downstream API while preserving the user's identity.

The key difference from custom token exchange is that this uses the simpler `get_token_on_behalf_of` method with just the `access_token`, `audience`, and optional `scope` parameters, making it more straightforward for the on-behalf-of use case.

### 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
))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
To enable your tenant to use the On-Behalf-Of Token Exchange:

1. Navigate to **Applications > Applications** and select your MCP client. Only Custom API clients associated with a resource server can use the OBO token exchange.
2. Under **Token Exchange**, toggle on **On-Behalf-Of Token Exchange**.
3. Select **Save**.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enable your tenant to use the On-Behalf-Of Token Exchange:

1. Navigate to **Applications > Applications** and select your MCP client. Only Custom API clients associated with a resource server can use the OBO token exchange.
2. Under **Token Exchange**, toggle on **On-Behalf-Of Token Exchange**.
3. Select **Save**.
Loading
Loading