Skip to content
223 changes: 83 additions & 140 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,12 @@ import CreateMCPAPI from "/snippets/mcp/get-started/create-mcp-api.mdx";
import AssignPermissionsToRoles from "/snippets/mcp/get-started/config-tenant/assign-permissions-to-roles.mdx";
import { DownloadQuickstartButton } from "/snippets/download-quickstart/DownloadQuickstartButton.jsx";
import MCPGetStartedTestingInstructions from "/snippets/mcp/get-started/testing-instructions.mdx";
import CreateEnvFile from "/snippets/mcp/get-started/call-your-apis/create-env-file.mdx";
import CustomTokenExchangeAction from "/snippets/mcp/get-started/call-your-apis/custom-token-exchange-action.mdx";
import RunMcpServerJs from "/snippets/mcp/get-started/call-your-apis/run-mcp-server-js.mdx";
import RunMcpServerPython from "/snippets/mcp/get-started/call-your-apis/run-mcp-server-python.mdx";
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.

Expand All @@ -19,7 +25,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
* Verify the access token using the `@auth0/auth0-api-js` library (which uses `jose` under the hood) against your tenant JWKS, enforcing issuer, audience, and RS256
* 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 Down Expand Up @@ -89,173 +95,110 @@ auth0 api post resource-servers --data '{

Save the `Audience` from the command output; you'll need it in a later step.


## Sample app
<Tabs>
<Tab title="Use sample app (recommended)">
Start by downloading the sample app for this quickstart. The sample includes a FastMCP MCP server with an Auth0 integration and a protected API built with
[Fastify](https://fastify.dev/).

<DownloadQuickstartButton
category="auth-for-mcp"
framework="fastmcp-mcp-customtokenexchange-js"
/>

Once downloaded, extract the files and open the project in your preferred IDE.
</Tab>
<Tab title="Clone GitHub repository">
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
```

Once cloned, open the project in your preferred IDE.
</Tab>
</Tabs>
<Tab title="Javascript" icon="js">
<Tabs>
<Tab title="Use sample app (recommended)">
Start by downloading the sample app for this quickstart. The sample includes a FastMCP MCP server with an Auth0 integration and a protected API built with
[Fastify](https://fastify.dev/).

The sample app demonstrates custom token exchange with a `greet` tool that calls your protected API on behalf of the authenticated user.
<DownloadQuickstartButton
category="auth-for-mcp"
framework="fastmcp-mcp-customtokenexchange-js"
/>

## 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:

```shell
npm install
```
Once downloaded, extract the files and open the project in your preferred IDE.
</Tab>
<Tab title="Clone GitHub repository">
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/).

## Create your environment file

In the `fastmcp-mcp-customtokenexchange-js` directory, run the following command to create a new `.env` file populated with all the required environment variables:

```shell wrap lines expandable
CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) \
&& CLIENT_SECRET=$(jq -r '.client_secret' auth0-app-details.json) \
&& DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') \
&& touch .env \
&& echo "AUTH0_DOMAIN=${DOMAIN}" > .env \
&& echo "AUTH0_AUDIENCE=http://localhost:3001/" >> .env \
&& echo "PORT=3001" >> .env \
&& 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=read:tasks" >> .env \
&& echo "API_AUTH0_AUDIENCE=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
```

To get your Auth0 application’s `AUTH0_DOMAIN`, run the following command:

```shell
auth0 tenants list
```
```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
```

Copy the domain under `TENANT` from the output and update the corresponding variable in the `.env` file.
Once cloned, open the project in your preferred IDE.
</Tab>
</Tabs>

For `MCP_AUTH0_CLIENT_ID` and `MCP_AUTH0_CLIENT_SECRET` you will use the values obtained from the [Create an Application for your MCP server](./call-your-apis-on-users-behalf#create-an-application-for-your-mcp-server) step.
The sample app demonstrates custom token exchange with a `greet` tool that calls your protected API on behalf of the authenticated user.

## Use Custom Token Exchange Action
## Install packages

This Action is the server-side logic Auth0 executes to perform the token exchange. It is necessary because the MCP server receives an access token from the client (with the MCP server as its audience) and must exchange it for a new token (with the upstream API as the audience). This Action validates the original token and mints the new one.
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:

The Custom Token Exchange Action is available as part of Custom Token Exchange Early Access. Navigate to [the On-behalf-of token exchange for first-party apps template available here](https://manage.auth0.com/#/actions/library/templates/templates/daeda4e8-8da2-4abb-afb5-ac09df0ebb2a) and click on **Use This Template**.

<Frame>
<img
src="/img/mcp/cte_action_template_page.png"
alt="Action On-behalf-of token exchange for first-party apps template page"
/>
</Frame>

This will open a modal for you to name the action:

<Frame>
<img
class="img-sizing"
src="/img/mcp/cte_action_creation_modal.png"
alt="Action creation modal"
/>
</Frame>

Once the action is created, you can **Deploy** it. When you deploy the Action, Auth0 assigns it an Action ID. You still need to add your custom logic to the Action, but first, get the Action ID to create the Custom Token Exchange Profile.
```shell
npm install
```

## Set up the token exchange profile
## Create your environment file
<CreateEnvFile/>

<CreateProfile />
## Use Custom Token Exchange Action
<CustomTokenExchangeAction/>

## Run the MCP server and the API
## Set up the token exchange profile
<CreateProfile />

Run this command to start your server:
## Run the MCP server and the API
<RunMcpServerJs/>

```shell
npm run start
```
## Exchange your access token
<ExchangeAccessTokenJs/>
</Tab>

And in a separate window run this command to start the API:
<Tab title="Python" icon="python">
<Tabs>
<Tab title="Use sample app (recommended)">
Start by downloading the sample app for this quickstart. The sample includes a FastMCP MCP server with an Auth0 integration and a protected Starlette-based API.

```shell
npm run start:api
```
<DownloadQuickstartButton
category="auth-for-mcp"
framework="fastmcp-mcp-customtokenexchange-python"
/>

## Exchange your access token
Once downloaded, extract the files and open the project in your preferred IDE.
</Tab>
<Tab title="Clone GitHub repository">
Clone the repository and navigate to the sample app folder which includes a FastMCP MCP server with an Auth0 integration and a protected Starlette-based API.

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).
```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
```

### The Orchestrator: `bearerForUpstream`
Once cloned, open the project in your preferred IDE.
</Tab>
</Tabs>

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

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

```shell wrap lines highlight={5}
async function bearerForUpstream(subjectToken: string) {
if (!subjectToken) return { token: null, scopes: null };
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:

try {
const result = await exchangeCustomToken(subjectToken);
return {
token: result.accessToken,
scopes: result.scope,
}
} catch (err) {
console.error('Error during token exchange:', err);
throw err;
}
}
```
```shell
poetry install
```

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.
## Create your environment file
<CreateEnvFile/>

### The core logic: `exchangeCustomToken`
## Use Custom Token Exchange Action
<CustomTokenExchangeAction/>

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.
## Set up the token exchange profile
<CreateProfile />

First, we initialize the `ApiClient` with the credentials of the application performing the exchange:
## Run the MCP server and the API
<RunMcpServerPython/>

```javascript wrap lines
const exchangeClient = new ApiClient({
domain: AUTH0_DOMAIN,
audience: API_AUTH0_AUDIENCE,
clientId: MCP_AUTH0_CLIENT_ID,
clientSecret: MCP_AUTH0_CLIENT_SECRET,
});
```
With the client configured, the `exchangeCustomToken` function calls the `getTokenByExchangeProfile` method. This method implements the [Custom Token Exchange](https://auth0.com/docs/authenticate/custom-token-exchange) flow.

```javascript wrap lines
export async function exchangeCustomToken(subjectToken: string) {
return await exchangeClient.getTokenByExchangeProfile(subjectToken, {
subjectTokenType: MCP_AUTH0_SUBJECT_TOKEN_TYPE,
audience: API_AUTH0_AUDIENCE,
...(MCP_AUTH0_EXCHANGE_SCOPE && { scope: MCP_AUTH0_EXCHANGE_SCOPE }),
});
}
```
## Exchange your access token
<ExchangeAccessTokenPython/>
</Tab>
</Tabs>

<MCPGetStartedTestingInstructions />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
From the root of your sample app directory, run the following command to create a new `.env` file populated with all the required environment variables:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we suggest to copy the .env.example file instead? There are some extra env variables set there that are missing in this block (which we can leave alone, but not too sure if we can omit them).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the command we have to create .env file is probably better here, because it populates all the required fields rather than copying and filling out the fields manually

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough, this particular one might not apply. But I see some other places in auth4genai/snippets/mcp/get-started/server-authorization.mdx where we instruct the developer to create a new .env file and manually add some variables to it. I suspect those ones should be improved, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, will update that page as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


```shell wrap lines expandable
CLIENT_ID=$(jq -r '.client_id' auth0-app-details.json) \
&& CLIENT_SECRET=$(jq -r '.client_secret' auth0-app-details.json) \
&& DOMAIN=$(auth0 tenants list --json | jq -r '.[] | select(.active == true) | .name') \
&& touch .env \
&& echo "AUTH0_DOMAIN=${DOMAIN}" > .env \
&& echo "AUTH0_AUDIENCE=http://localhost:3001/" >> .env \
&& echo "PORT=3001" >> .env \
&& 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 "API_AUTH0_AUDIENCE=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
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
This Action is the server-side logic Auth0 executes to perform the token exchange. It is necessary because the MCP server receives an access token from the client (with the MCP server as its audience) and must exchange it for a new token (with the upstream API as the audience). This Action validates the original token and mints the new one.

The Custom Token Exchange Action is available as part of Custom Token Exchange Early Access. Navigate to [the On-behalf-of token exchange for first-party apps template available here](https://manage.auth0.com/#/actions/library/templates/templates/daeda4e8-8da2-4abb-afb5-ac09df0ebb2a) and click on **Use This Template**.

<Frame>
<img
src="/img/mcp/cte_action_template_page.png"
alt="Action On-behalf-of token exchange for first-party apps template page"
/>
</Frame>

This will open a modal for you to name the action:

<Frame>
<img
class="img-sizing"
src="/img/mcp/cte_action_creation_modal.png"
alt="Action creation modal"
/>
</Frame>

Once the action is created, you can **Deploy** it. When you deploy the Action, Auth0 assigns it an Action ID. You still need to add your custom logic to the Action, but first, get the Action ID to create the Custom Token Exchange Profile.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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).

### 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.

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 };

try {
const result = await exchangeCustomToken(subjectToken);
return {
token: result.accessToken,
scopes: result.scope,
}
} catch (err) {
console.error('Error during token exchange:', err);
throw err;
}
}
```

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.

### The core logic: `exchangeCustomToken`

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.

First, we initialize the `ApiClient` with the credentials of the application performing the exchange:

```javascript wrap lines
const exchangeClient = new ApiClient({
domain: AUTH0_DOMAIN,
audience: API_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.

```javascript wrap lines
export async function exchangeCustomToken(subjectToken: string) {
return await exchangeClient.getTokenByExchangeProfile(subjectToken, {
subjectTokenType: MCP_AUTH0_SUBJECT_TOKEN_TYPE,
audience: API_AUTH0_AUDIENCE,
...(MCP_AUTH0_EXCHANGE_SCOPE && { scope: MCP_AUTH0_EXCHANGE_SCOPE }),
});
}
```
Loading