Skip to main content

Configure token exchange for backend authentication

This guide shows you how to configure token exchange in Kubernetes, which allows MCP servers to authenticate to backend APIs using short-lived, properly scoped tokens instead of embedded secrets.

For conceptual background on how token exchange works, see Backend authentication. For CLI-based setup, see Configure token exchange.

Prerequisites

You'll need:

Stacklok Enterprise

Microsoft Entra ID doesn't implement RFC 8693 token exchange. It has its own On-Behalf-Of (OBO) flow, based on the RFC 7523 JWT bearer grant, that solves the same backend-federation problem in a non-standard way. Stacklok Enterprise adds a dedicated obo authentication type for this flow.

Learn more about Stacklok Enterprise.

Configure your identity provider

Token exchange requires your identity provider to issue tokens for the backend service when presented with a valid MCP server token. The exact configuration steps vary by provider, but generally include:

Register a token exchange client

Create an OAuth application in your identity provider for ToolHive to use when performing token exchange:

  • Note the client ID and client secret
  • Grant the application permission to use the urn:ietf:params:oauth:grant-type:token-exchange grant type

Token exchange is an authenticated flow. ToolHive uses these credentials to prove its identity when requesting exchanged tokens from the identity provider.

Okta

Create an API Services application for ToolHive and enable the token exchange grant type in the application settings.

Define audience and scopes for the backend service

Configure your identity provider to recognize the backend service:

  • Define the audience value that identifies your backend service (for example, backend-api)
  • Specify the scopes the backend service accepts (for example, api:read, api:write)
Okta

Create a custom authorization server for the backend service and define the scopes under Security > API > Authorization Servers.

Create an access policy

Set up a policy that permits token exchange and controls what scopes are included in exchanged tokens:

  • Enable the token exchange grant type for the ToolHive client
  • Define which users or groups can obtain tokens for the backend service
  • Specify the scopes included in exchanged tokens
Okta

Add a trust relationship from the MCP authorization server to the backend authorization server, then create access policies on the backend server to permit token exchange.

Consult the Okta token exchange documentation for detailed steps.

Create the token exchange configuration

The steps below create an MCPExternalAuthConfig that exchanges the client's token for a backend-scoped one, and reference it from the MCPServer alongside the MCPOIDCConfig that validates the client's original token. Unlike the embedded auth server, token exchange doesn't issue the client a new token: it validates the one the client already has, then separately exchanges it for a different token when forwarding the request to the backend.

Step 1: Create a Secret for client credentials

Store the OAuth client secret that ToolHive uses to authenticate when performing token exchange:

token-exchange-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: token-exchange-secret
namespace: toolhive-system
type: Opaque
stringData:
client-secret: '<YOUR_CLIENT_SECRET>'
kubectl apply -f token-exchange-secret.yaml

Step 2: Create the MCPExternalAuthConfig resource

Create an MCPExternalAuthConfig resource that defines the token exchange parameters:

token-exchange-config.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPExternalAuthConfig
metadata:
name: backend-token-exchange
namespace: toolhive-system
spec:
type: tokenExchange
tokenExchange:
tokenUrl: '<YOUR_TOKEN_EXCHANGE_URL>'
audience: '<YOUR_BACKEND_AUDIENCE>'
clientId: '<YOUR_CLIENT_ID>'
clientSecretRef:
name: token-exchange-secret
key: client-secret
scopes:
- '<YOUR_REQUIRED_SCOPES>'
kubectl apply -f token-exchange-config.yaml

Configuration reference

FieldDescription
tokenUrlYour identity provider's token exchange endpoint
audienceTarget audience for the exchanged token (your backend service)
clientIdClient ID for ToolHive to authenticate to the IdP
clientSecretRefReference to the Secret containing the client secret
scopesScopes to request for the backend service

MCP server requirements

The MCP server that ToolHive fronts must accept a per-request authentication token. Specifically, the server should:

  • Read the access token from the Authorization: Bearer header
  • Use this token to authenticate to the backend service
  • Not rely on hardcoded secrets or environment variables for backend authentication

ToolHive injects the exchanged token into each request, so the MCP server receives a fresh, properly scoped token for every call.

Deploy an MCP server with token exchange

Create an MCPOIDCConfig resource and an MCPServer resource that references both the OIDC configuration and the token exchange configuration:

mcpserver-token-exchange.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPOIDCConfig
metadata:
name: token-exchange-oidc
namespace: toolhive-system
spec:
type: inline
inline:
issuer: '<YOUR_OIDC_ISSUER>'
jwksUrl: '<YOUR_JWKS_URL>'
---
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer
metadata:
name: my-mcp-server
namespace: toolhive-system
spec:
image: <YOUR_MCP_SERVER_IMAGE>
transport: streamable-http
proxyPort: 8080
# Reference the token exchange configuration
externalAuthConfigRef:
name: backend-token-exchange
# OIDC configuration for validating incoming client tokens
oidcConfigRef:
name: token-exchange-oidc
audience: '<YOUR_MCP_AUDIENCE>'
kubectl apply -f mcpserver-token-exchange.yaml

The externalAuthConfigRef tells ToolHive to use the token exchange configuration you created earlier. The oidcConfigRef validates incoming client tokens before performing the exchange.

Verify the configuration

To confirm token exchange is working:

  1. Check the MCPServer status:

    kubectl get mcpserver -n toolhive-system my-mcp-server
  2. Optionally, expose the server outside the cluster using an Ingress or Gateway

  3. Connect to the MCP server with a client that supports authentication

  4. Make a tool call that requires backend access

  5. Check the proxy logs for successful token exchange:

    kubectl logs -n toolhive-system -l app.kubernetes.io/name=my-mcp-server

You can also verify by examining your identity provider's logs for successful token exchange requests, or by checking audit logs on your backend service to confirm requests arrive with the correct user identity and scopes.

Example: Okta configuration

This example shows a complete configuration using Okta for token exchange.

Secret

okta-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: okta-token-exchange-secret
namespace: toolhive-system
type: Opaque
stringData:
client-secret: 'your-okta-client-secret'

MCPExternalAuthConfig

okta-token-exchange.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPExternalAuthConfig
metadata:
name: okta-backend-exchange
namespace: toolhive-system
spec:
type: tokenExchange
tokenExchange:
tokenUrl: 'https://dev-123456.okta.com/oauth2/aus9876543210/v1/token'
audience: 'backend-api'
clientId: '0oa0987654321fedcba'
clientSecretRef:
name: okta-token-exchange-secret
key: client-secret
scopes:
- 'api:read'
- 'api:write'

MCPOIDCConfig and MCPServer

mcpserver-okta.yaml
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPOIDCConfig
metadata:
name: okta-oidc
namespace: toolhive-system
spec:
type: inline
inline:
issuer: 'https://dev-123456.okta.com/oauth2/aus1234567890'
jwksUrl: 'https://dev-123456.okta.com/oauth2/aus1234567890/v1/keys'
---
apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer
metadata:
name: my-backend-server
namespace: toolhive-system
spec:
image: your-mcp-server:latest
transport: streamable-http
proxyPort: 8080
externalAuthConfigRef:
name: okta-backend-exchange
oidcConfigRef:
name: okta-oidc
# Set resourceUrl to the external URL if exposing outside the cluster
resourceUrl: 'https://my-backend-server.example.com'
audience: 'mcp-server'

Key points in this example:

  • Two authorization servers: The issuer in MCPOIDCConfig (aus1234567890) validates incoming client tokens. The tokenUrl in MCPExternalAuthConfig uses a different authorization server (aus9876543210) that issues tokens for the backend API.
  • Audience transformation: Client tokens arrive with audience mcp-server. ToolHive exchanges them for tokens with audience backend-api, which the backend service expects.
  • Scope transformation: The original token has MCP-specific scopes, while the exchanged token has backend-specific scopes (api:read, api:write). The user's identity is preserved, but the permissions are transformed for the target service.

Next steps