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
Before you begin, make sure you have:
- Kubernetes cluster with RBAC enabled
- ToolHive Operator installed (see Deploy the ToolHive Operator with Helm)
kubectlaccess to your cluster- 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
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. This involves:
- Registering a token exchange client with credentials
- Defining audience and scopes for the backend service
- Creating access policies that permit token exchange
For detailed IdP configuration steps, see Configure your identity provider in the CLI guide.
Create the token exchange configuration
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/v1alpha1
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 MCPServer resource that references the token exchange configuration:
apiVersion: toolhive.stacklok.dev/v1alpha1
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
oidcConfig:
type: inline
inline:
issuer: '<YOUR_OIDC_ISSUER>'
audience: '<YOUR_MCP_AUDIENCE>'
jwksUrl: '<YOUR_JWKS_URL>'
kubectl apply -f mcpserver-token-exchange.yaml
The externalAuthConfigRef tells ToolHive to use the token exchange
configuration you created earlier. The oidcConfig 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 -
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/v1alpha1
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'
MCPServer
apiVersion: toolhive.stacklok.dev/v1alpha1
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
oidcConfig:
type: inline
inline:
issuer: 'https://dev-123456.okta.com/oauth2/aus1234567890'
audience: 'mcp-server'
jwksUrl: 'https://dev-123456.okta.com/oauth2/aus1234567890/v1/keys'
Key points in this example:
- Two authorization servers: The
issuerinoidcConfig(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.
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