Skip to content

Commit efb1cda

Browse files
committed
Add Kubernetes token exchange guide
Document how to configure RFC 8693 token exchange for MCP servers in Kubernetes using the ToolHive Operator. Covers MCPExternalAuthConfig resource creation, Secret management, and MCPServer configuration with an Okta example. Fixes: #240
1 parent 826520c commit efb1cda

File tree

2 files changed

+260
-0
lines changed

2 files changed

+260
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
---
2+
title: Configure token exchange for backend authentication
3+
description:
4+
How to set up token exchange so MCP servers can authenticate to backend
5+
services in Kubernetes using the ToolHive Operator.
6+
---
7+
8+
This guide shows you how to configure token exchange in Kubernetes, which allows
9+
MCP servers to authenticate to backend APIs using short-lived, properly scoped
10+
tokens instead of embedded secrets.
11+
12+
For conceptual background on how token exchange works, see
13+
[Backend authentication](../concepts/backend-auth.mdx). For CLI-based setup, see
14+
[Configure token exchange](../guides-cli/token-exchange.mdx).
15+
16+
## Prerequisites
17+
18+
Before you begin, make sure you have:
19+
20+
- Kubernetes cluster with RBAC enabled
21+
- ToolHive Operator installed (see
22+
[Deploy the ToolHive Operator with Helm](./deploy-operator-helm.mdx))
23+
- `kubectl` access to your cluster
24+
- An identity provider that supports
25+
[RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) token exchange (such
26+
as Okta, Auth0, or Keycloak)
27+
- A backend service configured to accept tokens from your identity provider
28+
- Familiarity with
29+
[Authentication and authorization in Kubernetes](./auth-k8s.mdx)
30+
31+
## Configure your identity provider
32+
33+
Token exchange requires your identity provider to issue tokens for the backend
34+
service when presented with a valid MCP server token. This involves:
35+
36+
- Registering a token exchange client with credentials
37+
- Defining audience and scopes for the backend service
38+
- Creating access policies that permit token exchange
39+
40+
For detailed IdP configuration steps, see
41+
[Configure your identity provider](../guides-cli/token-exchange.mdx#configure-your-identity-provider)
42+
in the CLI guide.
43+
44+
## Create the token exchange configuration
45+
46+
### Step 1: Create a Secret for client credentials
47+
48+
Store the OAuth client secret that ToolHive uses to authenticate when performing
49+
token exchange:
50+
51+
```yaml title="token-exchange-secret.yaml"
52+
apiVersion: v1
53+
kind: Secret
54+
metadata:
55+
name: token-exchange-secret
56+
namespace: toolhive-system
57+
type: Opaque
58+
stringData:
59+
client-secret: '<YOUR_CLIENT_SECRET>'
60+
```
61+
62+
```bash
63+
kubectl apply -f token-exchange-secret.yaml
64+
```
65+
66+
### Step 2: Create the MCPExternalAuthConfig resource
67+
68+
Create an `MCPExternalAuthConfig` resource that defines the token exchange
69+
parameters:
70+
71+
```yaml title="token-exchange-config.yaml"
72+
apiVersion: toolhive.stacklok.dev/v1alpha1
73+
kind: MCPExternalAuthConfig
74+
metadata:
75+
name: backend-token-exchange
76+
namespace: toolhive-system
77+
spec:
78+
type: tokenExchange
79+
tokenExchange:
80+
tokenUrl: '<YOUR_TOKEN_EXCHANGE_URL>'
81+
audience: '<YOUR_BACKEND_AUDIENCE>'
82+
clientId: '<YOUR_CLIENT_ID>'
83+
clientSecretRef:
84+
name: token-exchange-secret
85+
key: client-secret
86+
scopes:
87+
- '<YOUR_REQUIRED_SCOPES>'
88+
```
89+
90+
```bash
91+
kubectl apply -f token-exchange-config.yaml
92+
```
93+
94+
### Configuration reference
95+
96+
| Field | Description |
97+
| ----------------- | -------------------------------------------------------------- |
98+
| `tokenUrl` | Your identity provider's token exchange endpoint |
99+
| `audience` | Target audience for the exchanged token (your backend service) |
100+
| `clientId` | Client ID for ToolHive to authenticate to the IdP |
101+
| `clientSecretRef` | Reference to the Secret containing the client secret |
102+
| `scopes` | Scopes to request for the backend service |
103+
104+
## MCP server requirements
105+
106+
The MCP server that ToolHive fronts must accept a per-request authentication
107+
token. Specifically, the server should:
108+
109+
- Read the access token from the `Authorization: Bearer` header
110+
- Use this token to authenticate to the backend service
111+
- Not rely on hardcoded secrets or environment variables for backend
112+
authentication
113+
114+
ToolHive injects the exchanged token into each request, so the MCP server
115+
receives a fresh, properly scoped token for every call.
116+
117+
## Deploy an MCP server with token exchange
118+
119+
Create an `MCPServer` resource that references the token exchange configuration:
120+
121+
```yaml title="mcpserver-token-exchange.yaml"
122+
apiVersion: toolhive.stacklok.dev/v1alpha1
123+
kind: MCPServer
124+
metadata:
125+
name: my-mcp-server
126+
namespace: toolhive-system
127+
spec:
128+
image: <YOUR_MCP_SERVER_IMAGE>
129+
transport: streamable-http
130+
proxyPort: 8080
131+
# Reference the token exchange configuration
132+
externalAuthConfigRef:
133+
name: backend-token-exchange
134+
# OIDC configuration for validating incoming client tokens
135+
oidcConfig:
136+
type: inline
137+
inline:
138+
issuer: '<YOUR_OIDC_ISSUER>'
139+
audience: '<YOUR_MCP_AUDIENCE>'
140+
jwksUrl: '<YOUR_JWKS_URL>'
141+
```
142+
143+
```bash
144+
kubectl apply -f mcpserver-token-exchange.yaml
145+
```
146+
147+
The `externalAuthConfigRef` tells ToolHive to use the token exchange
148+
configuration you created earlier. The `oidcConfig` validates incoming client
149+
tokens before performing the exchange.
150+
151+
## Verify the configuration
152+
153+
To confirm token exchange is working:
154+
155+
1. Check the MCPServer status:
156+
157+
```bash
158+
kubectl get mcpserver -n toolhive-system my-mcp-server
159+
```
160+
161+
2. Connect to the MCP server with a client that supports authentication
162+
163+
3. Make a tool call that requires backend access
164+
165+
4. Check the proxy logs for successful token exchange:
166+
167+
```bash
168+
kubectl logs -n toolhive-system -l app.kubernetes.io/name=my-mcp-server
169+
```
170+
171+
You can also verify by examining your identity provider's logs for successful
172+
token exchange requests, or by checking audit logs on your backend service to
173+
confirm requests arrive with the correct user identity and scopes.
174+
175+
## Example: Okta configuration
176+
177+
This example shows a complete configuration using Okta for token exchange.
178+
179+
### Secret
180+
181+
```yaml title="okta-secret.yaml"
182+
apiVersion: v1
183+
kind: Secret
184+
metadata:
185+
name: okta-token-exchange-secret
186+
namespace: toolhive-system
187+
type: Opaque
188+
stringData:
189+
client-secret: 'your-okta-client-secret'
190+
```
191+
192+
### MCPExternalAuthConfig
193+
194+
```yaml title="okta-token-exchange.yaml"
195+
apiVersion: toolhive.stacklok.dev/v1alpha1
196+
kind: MCPExternalAuthConfig
197+
metadata:
198+
name: okta-backend-exchange
199+
namespace: toolhive-system
200+
spec:
201+
type: tokenExchange
202+
tokenExchange:
203+
tokenUrl: 'https://dev-123456.okta.com/oauth2/aus9876543210/v1/token'
204+
audience: 'backend-api'
205+
clientId: '0oa0987654321fedcba'
206+
clientSecretRef:
207+
name: okta-token-exchange-secret
208+
key: client-secret
209+
scopes:
210+
- 'api:read'
211+
- 'api:write'
212+
```
213+
214+
### MCPServer
215+
216+
```yaml title="mcpserver-okta.yaml"
217+
apiVersion: toolhive.stacklok.dev/v1alpha1
218+
kind: MCPServer
219+
metadata:
220+
name: my-backend-server
221+
namespace: toolhive-system
222+
spec:
223+
image: your-mcp-server:latest
224+
transport: streamable-http
225+
proxyPort: 8080
226+
externalAuthConfigRef:
227+
name: okta-backend-exchange
228+
oidcConfig:
229+
type: inline
230+
inline:
231+
issuer: 'https://dev-123456.okta.com/oauth2/aus1234567890'
232+
audience: 'mcp-server'
233+
jwksUrl: 'https://dev-123456.okta.com/oauth2/aus1234567890/v1/keys'
234+
```
235+
236+
Key points in this example:
237+
238+
- **Two authorization servers**: The `issuer` in `oidcConfig` (`aus1234567890`)
239+
validates incoming client tokens. The `tokenUrl` in `MCPExternalAuthConfig`
240+
uses a different authorization server (`aus9876543210`) that issues tokens for
241+
the backend API.
242+
- **Audience transformation**: Client tokens arrive with audience `mcp-server`.
243+
ToolHive exchanges them for tokens with audience `backend-api`, which the
244+
backend service expects.
245+
- **Scope transformation**: The original token has MCP-specific scopes, while
246+
the exchanged token has backend-specific scopes (`api:read`, `api:write`). The
247+
user's identity is preserved, but the permissions are transformed for the
248+
target service.
249+
250+
## Related information
251+
252+
- [Backend authentication](../concepts/backend-auth.mdx) - conceptual overview
253+
of token exchange and federation
254+
- [Configure token exchange (CLI)](../guides-cli/token-exchange.mdx) - CLI-based
255+
setup
256+
- [Authentication and authorization](./auth-k8s.mdx) - basic auth setup for MCP
257+
servers in Kubernetes
258+
- [MCPExternalAuthConfig reference](../reference/operator/mcpexternalauthconfig.md) -
259+
complete CRD specification

sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ const sidebars: SidebarsConfig = {
158158
'toolhive/guides-k8s/telemetry-and-metrics',
159159
'toolhive/guides-k8s/logging',
160160
'toolhive/guides-k8s/auth-k8s',
161+
'toolhive/guides-k8s/token-exchange-k8s',
161162
'toolhive/reference/crd-spec',
162163
],
163164
},

0 commit comments

Comments
 (0)