Skip to main content
Version: 1.8.0

Entra On-Behalf-Of Token Exchange

Some upstream MCP servers and REST APIs authorize each end user, and accept only a token Microsoft Entra ID issued for them. With auth_type: oauth2_token_exchange and token_exchange_profile: entra_obo, AISIX obtains that token on the caller's behalf. The caller signs in to Entra and presents an Entra token for the gateway. AISIX verifies it, exchanges it at Entra with the On-Behalf-Of (OBO) flow, and sends the resulting token to the upstream.

The caller's token is sent only to the configured Entra token endpoint, as the OBO request's assertion. It is never sent to the upstream. The upstream receives only the exchanged token, as Authorization: Bearer <access_token>.

The exchange applies to type: mcp and type: openapi servers.

How It Works​

  1. The client sends an Entra access token issued for the gateway's app registration, as Authorization: Bearer <token>.
  2. A JWT trust provider verifies the token and maps it to a caller API key. That key's MCP access rules decide which servers and tools the caller reaches.
  3. For a server the caller can reach, AISIX posts the OBO request to the server's token_url. The request is form-encoded, with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer, the caller's token as assertion, the configured scopes joined by spaces as scope, requested_token_use=on_behalf_of, and the configured client_id and client secret.
  4. AISIX sends the access token Entra returns to the upstream as Authorization: Bearer <access_token>, and caches it for later requests from the same caller.

Only a token that a JWT trust provider verified on the same request is exchanged. A caller authenticated any other way has nothing to exchange, and AISIX does not contact the token endpoint for it. This covers a caller API key, an anonymous MCP entry, and a gateway key sent in x-aisix-api-key with an Entra token in Authorization. In the last case AISIX authenticates the key and does not verify the token.

Prerequisites​

Before starting, prepare the following:

  • A gateway released after 1.7.1 in every environment that serves the server. See Gateway Version Requirement.
  • Complete Set Up MCP Gateway for AISIX Enterprise, AISIX Cloud, or an open-source AISIX gateway.
  • A Microsoft Entra ID tenant, and permission to register applications and grant consent in it.
  • The downstream API the upstream server accepts tokens for, registered in Entra with at least one delegated permission (scope).
  • A client that signs users in to Entra and obtains an access token for the gateway's app registration. The OBO flow works for user tokens only, so an app-only token obtained with client credentials cannot be exchanged.

Register the Gateway in Entra ID​

In the OBO flow, AISIX is the middle-tier API: clients request tokens for it, and it requests tokens for the downstream API. Microsoft's On-Behalf-Of flow documentation is the authoritative description. The steps below are the parts AISIX depends on.

  1. In the Microsoft Entra admin center, register an application for the gateway. Record its Application (client) ID and the Directory (tenant) ID.
  2. Under Expose an API, set an Application ID URI and add a scope, such as access_as_user. Clients request a token for this scope, for example api://<gateway-client-id>/access_as_user. Entra accepts an OBO request only for a token whose audience is the app making the request, so the client's token must be issued for this registration.
  3. Under Certificates & secrets, create a client secret. AISIX authenticates to the token endpoint with the client ID and this secret.
  4. Under API permissions, add the downstream API's delegated permission.
  5. Grant consent for that permission. A tenant administrator can grant admin consent on the same page. Alternatively, list the client application in the gateway registration's knownClientApplications so that users consent to both in one prompt. See Gaining consent for the middle-tier application.

The token endpoint for the tenant is https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token.

Trust Entra Tokens at the Gateway​

AISIX can exchange only a token it has verified. Register the Entra tenant as a JWT trust provider in the environment, as described in JWT Authentication:

  • Set issuer to the iss claim of the tokens clients present. For a v2.0 access token, it is https://login.microsoftonline.com/<tenant-id>/v2.0.
  • Set audiences to the aud claim of those tokens. For a v2.0 access token, it is the gateway's application (client) ID.

Decode a sample token from your client and copy both values from it, because Entra issues v1.0 or v2.0 tokens depending on the app registration, and the two versions differ in iss and aud. See Microsoft's access token claims reference.

Then map each verified identity to a caller API key, either by binding a key to a subject or with claim mappings. Grant that key the MCP tools it needs; see Control Tool Access.

Register the MCP Server​

Configure the server with these fields:

FieldValue
auth_typeoauth2_token_exchange.
token_exchange_profileentra_obo, the only supported profile.
token_urlThe tenant's token endpoint.
client_idThe gateway registration's application (client) ID.
secretThe gateway registration's client secret.
scopesAt least one scope of the downstream API, such as api://<downstream-app-id>/.default. AISIX joins the entries with spaces.
timeout_msOptional. Also bounds the exchange; see Timeouts.

All fields except timeout_ms are required for this auth type.

AISIX Enterprise or AISIX Cloud​

Export the control-plane connection values used in Upstream Authentication, then create the server:

curl -sS -X POST "$AISIX_CP/mcp_servers" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "orders",
"url": "https://orders.example.com/mcp",
"auth_type": "oauth2_token_exchange",
"token_exchange_profile": "entra_obo",
"token_url": "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token",
"client_id": "YOUR_GATEWAY_CLIENT_ID",
"secret": "YOUR_GATEWAY_CLIENT_SECRET",
"scopes": ["api://YOUR_DOWNSTREAM_APP_ID/.default"],
"allowed_environments": ["'"$ENV_ID"'"]
}'

❶ The Entra On-Behalf-Of exchange.

❷ The tenant's token endpoint and the gateway registration's credentials. The client secret is stored encrypted and never returned.

❸ The downstream API's scope.

In the dashboard, choose Token exchange (Entra OBO) as the server's Authentication. The form asks for the exchange profile, client ID, token URL, client secret, and scopes. When you edit the server, leave the client secret blank to keep the stored one.

AISIX OSS​

Add the server to mcp_servers, and supply the client secret through environment interpolation:

resources.yaml (Entra On-Behalf-Of exchange)
mcp_servers:
- name: orders
type: mcp
url: https://orders.example.com/mcp
auth_type: oauth2_token_exchange
token_exchange_profile: entra_obo
token_url: https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token
client_id: YOUR_GATEWAY_CLIENT_ID
secret: ${ENTRA_OBO_CLIENT_SECRET}
scopes:
- api://YOUR_DOWNSTREAM_APP_ID/.default

The same file needs the trust provider in oidc_providers and the JWT-bound caller API key in api_keys, as shown in JWT Authentication.

Connect a Client​

The client sends its Entra token for the gateway as the gateway credential:

curl -sS -X POST "$AISIX_PROXY/mcp/orders" \
-H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

On /mcp/{server}, AISIX runs the exchange as soon as the client sends initialize or server/discover, so a client learns about an exchange failure from its first request.

Token Caching and Retries​

AISIX caches each exchanged token in the gateway process, separately for each caller token, server, and exchange configuration. A cached token is reused until 60 seconds before the expires_in Entra reported, or 3,600 seconds when the response has none, and is never kept longer than 30 days.

Concurrent requests that miss the cache for the same caller and server share one request to the token endpoint and receive its outcome. A failed exchange is not cached: the next request asks Entra again.

If the upstream rejects an exchanged token with 401, AISIX discards the cached token, exchanges again, and retries once with the new token. A response from Entra without a usable access token, or with a token_type other than Bearer, is not used.

Timeouts​

The exchange is bounded by the server's timeout_ms, the same deadline as its other upstream calls, or 30 seconds when timeout_ms is unset. The request to the token endpoint also has its own 30-second limit, so a timeout_ms above 30 seconds does not give the exchange more time. On a type: mcp tool call or tool listing, the exchange and the upstream connection that follows it share one deadline.

An exchange that runs out of time fails as token_exchange_unavailable.

Errors​

On /mcp/{server}, a failed exchange is answered with an HTTP status and an OpenAI-style error body whose error.code names the failure:

Statuserror.codeCause
401token_exchange_subject_missingThe caller has no verified token to exchange, for example because it authenticated with a caller API key.
401token_exchange_subject_rejectedEntra rejected the caller's token with a 4xx response not listed in the next row.
500token_exchange_misconfiguredEntra answered invalid_client, unauthorized_client, unsupported_grant_type, invalid_target, or invalid_scope. The gateway's registration, secret, or scopes are wrong.
503token_exchange_unavailableEntra answered 429 or 408, answered 5xx, could not be reached, did not answer within the deadline, or returned no usable token.

The error_description Entra returns is never passed to the caller or written to the gateway logs.

On /mcp/{server} for a server outside the caller's grant, AISIX does not attempt the exchange. The caller gets the ordinary access-control outcome, such as an empty tools/list or a refused tool call, rather than a token-exchange error. A deny rule that covers the whole server with a trailing *, such as orders__* or *, places the server outside the grant.

On the aggregated /mcp, a failed exchange affects only that server. Its tools drop out of tools/list, and a call to one of them fails as a tool error. Other servers keep working.

Conditional Access Step-Up​

When the environment publishes MCP OAuth discovery, both 401 responses carry a WWW-Authenticate challenge that points the client at the discovery metadata:

WWW-Authenticate: Bearer error="invalid_token", resource_metadata="https://gateway.example.com/.well-known/oauth-protected-resource/mcp"

Entra can reject an exchange because the downstream API requires more than the caller's token proves, such as multi-factor authentication required by a Conditional Access policy. Its rejection then carries a claims challenge, and AISIX answers 401 token_exchange_subject_rejected with a step-up challenge that hands the claims to the client, base64-encoded:

WWW-Authenticate: Bearer resource_metadata="https://gateway.example.com/.well-known/oauth-protected-resource/mcp", error="insufficient_claims", error_description="Step-up authentication required; satisfy the returned claims challenge with the IdP and retry", claims="eyJhY2Nlc3NfdG9rZW4iOnsiY2Fwb2xpZHMiOnsiZXNzZW50aWFsIjp0cnVlLCJ2YWx1ZXMiOlsiYzEiXX19fQ=="

The client decodes the claims, requests a new token from Entra with them, and retries with that token. Without MCP OAuth discovery, the 401 responses carry no WWW-Authenticate challenge.

Gateway Version Requirement​

oauth2_token_exchange requires a gateway released after 1.7.1. A gateway at 1.7.1 or earlier cannot load a server that uses it. While any gateway registered in the target environment runs 1.7.1 or earlier, the AISIX Cloud Admin API and the dashboard refuse to save such a server with 422 and the error code DP_INCOMPATIBLE. Upgrade the gateways in that environment first.

Next Steps​