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:
- An identity provider that supports RFC 8693 token exchange (such as Okta, Auth0, or Keycloak)
- A backend service configured to accept tokens from your identity provider
- Familiarity with Authentication and authorization in Kubernetes
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.
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-exchangegrant type
Token exchange is an authenticated flow. ToolHive uses these credentials to prove its identity when requesting exchanged tokens from the identity provider.
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)
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
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:
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:
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
| Field | Description |
|---|---|
tokenUrl | Your identity provider's token exchange endpoint |
audience | Target audience for the exchanged token (your backend service) |
clientId | Client ID for ToolHive to authenticate to the IdP |
clientSecretRef | Reference to the Secret containing the client secret |
scopes | Scopes 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: Bearerheader - 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:
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:
-
Check the MCPServer status:
kubectl get mcpserver -n toolhive-system my-mcp-server -
Optionally, expose the server outside the cluster using an Ingress or Gateway
-
Connect to the MCP server with a client that supports authentication
-
Make a tool call that requires backend access
-
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
apiVersion: v1
kind: Secret
metadata:
name: okta-token-exchange-secret
namespace: toolhive-system
type: Opaque
stringData:
client-secret: 'your-okta-client-secret'
MCPExternalAuthConfig
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
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
issuerinMCPOIDCConfig(aus1234567890) validates incoming client tokens. ThetokenUrlinMCPExternalAuthConfiguses 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 audiencebackend-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
- Monitor server activity with OpenTelemetry and Prometheus
- Set up audit logging to track requests and authentication decisions
Related information
- Backend authentication - conceptual overview of token exchange and federation
- Configure token exchange (CLI) - CLI-based setup
- Authentication and authorization - basic auth setup for MCP servers in Kubernetes
- CRD specification - complete CRD reference including MCPExternalAuthConfig
- AWS STS integration - for AWS services,
ToolHive has built-in STS support using
MCPExternalAuthConfigwithtype: awsSts - Combine embedded auth with outgoing token exchange -
set both
authServerRefandexternalAuthConfigRefon the same MCPServer to pair the two