Authentication and authorization
This guide shows you how to secure your MCP servers in Kubernetes using authentication and authorization with the ToolHive Operator.
Authentication and authorization are emerging capabilities in the MCP ecosystem. The official MCP authorization specification is still evolving, and client support for these features is limited. ToolHive is leading the way in implementing these capabilities, but you may encounter some limitations with certain clients.
Prerequisites
You'll need:
- Kubernetes cluster with RBAC enabled
- ToolHive Operator installed (see Deploy the ToolHive Operator with Helm)
kubectl
access to your cluster
Choose your authentication approach
There are two main ways to authenticate with MCP servers running in Kubernetes:
Approach 1: External identity provider authentication
Use this when you want to authenticate users or external services using providers like Google, GitHub, Microsoft Entra ID, Okta, or Auth0.
Prerequisites for external IdP:
Before you begin, make sure you have:
- ToolHive installed and working
- Basic familiarity with OAuth, OIDC, and JWT concepts
- An identity provider that supports OpenID Connect (OIDC), such as Google, GitHub, Microsoft Entra ID (Azure AD), Okta, Auth0, or Kubernetes (for service accounts)
From your identity provider, you'll need:
- Client ID
- Audience value
- Issuer URL
- JWKS URL (for key verification)
ToolHive uses OIDC to connect to your existing identity provider, so you can authenticate with your own credentials (for example, Google login) or with service account tokens (for example, in Kubernetes). ToolHive never sees your password, only signed tokens from your identity provider.
For background on authentication, authorization, and Cedar policy examples, see Authentication and authorization framework.
Approach 2: Kubernetes service-to-service authentication
Use this when you have client applications running in the same Kubernetes cluster that need to call MCP servers. This approach uses Kubernetes service account tokens for authentication.
Prerequisites for service-to-service:
- Client applications running in Kubernetes pods
- Understanding of Kubernetes service accounts and RBAC
Set up external identity provider authentication
Step 1: Create an MCPServer with external OIDC
Create an MCPServer
resource configured to accept tokens from your external
identity provider. The ToolHive proxy will handle authentication before
forwarding requests to the MCP server.
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: weather-server-external
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/weather-mcp/server
transport: sse
port: 8080
permissionProfile:
type: builtin
name: network
# Authentication configuration for external IdP
auth:
oidc:
audience: '<your-audience>'
clientId: '<your-client-id>'
issuer: '<https://your-oidc-issuer.com>'
jwksUrl: '<https://your-oidc-issuer.com/path/to/jwks>'
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'
Replace the OIDC placeholders with your actual identity provider configuration.
Step 2: Apply the MCPServer resource
kubectl apply -f mcp-server-external-auth.yaml
Step 3: Test external authentication
Clients connecting to this MCP server must include a valid JWT token from your configured identity provider in their requests. The ToolHive proxy will validate the token before allowing access to the MCP server.
:::note Obtaining JWT tokens
How to obtain JWT tokens varies by identity provider and is outside the scope of this guide. Consult your identity provider's documentation for specific instructions on:
- Interactive user authentication flows (OAuth 2.0 Authorization Code flow)
- Service-to-service authentication (Client Credentials flow)
- API token generation and management
For Kubernetes service accounts, tokens are automatically mounted at
/var/run/secrets/kubernetes.io/serviceaccount/token
in pods.
:::
Set up Kubernetes service-to-service authentication
This approach is ideal when you have client applications running in the same Kubernetes cluster that need to call MCP servers.
Step 1: Create service account for client application
Create a service account that your client application will use:
apiVersion: v1
kind: ServiceAccount
metadata:
name: mcp-client
namespace: client-apps
kubectl apply -f client-service-account.yaml
Step 2: Create MCPServer for service-to-service auth
Create an MCPServer
resource configured to accept Kubernetes service account
tokens:
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: weather-server-k8s
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/weather-mcp/server
transport: sse
port: 8080
permissionProfile:
type: builtin
name: network
# Authentication configuration for Kubernetes service accounts
auth:
oidc:
audience: 'toolhive'
clientId: 'mcp-client.client-apps.svc.cluster.local'
issuer: 'https://kubernetes.default.svc'
jwksUrl: 'https://kubernetes.default.svc/openid/v1/jwks'
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'
This configuration only allows requests from pods using the mcp-client
service
account in the client-apps
namespace.
kubectl apply -f mcp-server-k8s-auth.yaml
Step 3: Deploy client application with service account
Deploy your client application using the service account:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-client-app
namespace: client-apps
spec:
replicas: 1
selector:
matchLabels:
app: mcp-client-app
template:
metadata:
labels:
app: mcp-client-app
spec:
serviceAccountName: mcp-client
containers:
- name: client
image: your-client-app:latest
env:
- name: MCP_SERVER_URL
value: 'http://weather-server-k8s.toolhive-system.svc.cluster.local:8080'
kubectl apply -f client-app.yaml
Your client application can now authenticate to the MCP server using its
Kubernetes service account token, which is automatically mounted at
/var/run/secrets/kubernetes.io/serviceaccount/token
.
Set up authorization
Both authentication approaches can use the same authorization configuration using Cedar policies.
Step 1: Create authorization configuration
Create a JSON or YAML file with Cedar policies. Here's an example in JSON format:
{
"version": "1.0",
"type": "cedarv1",
"cedar": {
"policies": [
// Allow everyone to use the weather tool
"permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
// Restrict admin_tool to a specific user
"permit(principal == Client::\"alice123\", action == Action::\"call_tool\", resource == Tool::\"admin_tool\");",
// Role-based access: only users with the 'premium' role can call any tool
"permit(principal, action == Action::\"call_tool\", resource) when { principal.claim_roles.contains(\"premium\") };",
// Attribute-based: allow calculator tool only for add/subtract operations
"permit(principal, action == Action::\"call_tool\", resource == Tool::\"calculator\") when { resource.arg_operation == \"add\" || resource.arg_operation == \"subtract\" };"
],
"entities_json": "[]"
}
}
You can also define custom resource attributes in entities_json
for per-tool
ownership or sensitivity labels.
For more policy examples and advanced usage, see Cedar policies.
Step 2: Create a ConfigMap with policies
Store your authorization configuration in a ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: authz-config
namespace: toolhive-system
data:
authz-config.json: |
{
"version": "1.0",
"type": "cedarv1",
"cedar": {
"policies": [
"permit(principal, action == Action::\"call_tool\", resource == Tool::\"weather\");",
"permit(principal == Client::\"alice123\", action == Action::\"call_tool\", resource == Tool::\"admin_tool\");",
"permit(principal, action == Action::\"call_tool\", resource) when { principal.claim_roles.contains(\"premium\") };"
],
"entities_json": "[]"
}
}
kubectl apply -f authz-configmap.yaml
Step 3: Update MCPServer to use authorization
Add the authorization configuration to your MCPServer
resources:
apiVersion: toolhive.stacklok.dev/v1alpha1
kind: MCPServer
metadata:
name: weather-server-with-authz
namespace: toolhive-system
spec:
image: ghcr.io/stackloklabs/weather-mcp/server
transport: sse
port: 8080
permissionProfile:
type: builtin
name: network
# Authentication configuration
auth:
oidc:
audience: 'toolhive'
clientId: 'mcp-client.client-apps.svc.cluster.local'
issuer: 'https://kubernetes.default.svc'
jwksUrl: 'https://kubernetes.default.svc/openid/v1/jwks'
# Authorization configuration
authorization:
configMapName: authz-config
configMapKey: authz-config.json
resources:
limits:
cpu: '100m'
memory: '128Mi'
requests:
cpu: '50m'
memory: '64Mi'
kubectl apply -f mcp-server-with-authz.yaml
Test your setup
Test external IdP authentication
- Deploy the external IdP configuration
- Obtain a valid JWT token from your identity provider
- Make a request to the MCP server including the token
Test service-to-service authentication
- Deploy both the MCP server and client application
- Check that the client can successfully call the MCP server
- Verify authentication in the ToolHive proxy logs:
kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s
Test authorization
- Make requests that should be permitted by your policies
- Make requests that should be denied
- Check the proxy logs to see authorization decisions
Troubleshooting
Authentication issues
If clients can't authenticate:
-
Check that the JWT token is valid and not expired
-
Verify that the audience and issuer match your configuration
-
Ensure the JWKS URL is accessible
-
Check the server logs for specific authentication errors:
thv logs <server-name>
Authorization issues
If authenticated clients are denied access:
- Make sure your Cedar policies explicitly permit the specific action (remember, default deny)
- Check that the principal, action, and resource match what's in your policies (including case and formatting)
- Examine any conditions in your policies to ensure they're satisfied (for example, required JWT claims or tool arguments)
- Remember that Cedar uses a default deny policy—if no policy explicitly permits an action, it will be denied
Troubleshooting tip: If access is denied, check that your policies explicitly permit the action. Cedar uses a default deny model—if no policy matches, the request is denied.
Kubernetes-specific issues
MCPServer resource issues:
- Check the MCPServer status:
kubectl get mcpserver -n toolhive-system
- Describe the resource for details:
kubectl describe mcpserver weather-server-k8s -n toolhive-system
Service account issues:
- Verify the service account exists:
kubectl get sa -n client-apps mcp-client
- Check RBAC permissions if needed
ConfigMap mounting issues:
- Verify the ConfigMap exists:
kubectl get configmap -n toolhive-system authz-config
- Check the ConfigMap content:
kubectl get configmap authz-config -n toolhive-system -o yaml
OIDC configuration issues:
- For external IdP: Ensure the issuer URL is accessible from within the cluster
- For Kubernetes auth: Ensure the Kubernetes API server has OIDC enabled
- Check that the JWKS URL returns valid keys
Network connectivity:
- Verify pods can reach the Kubernetes API server
- Check cluster DNS resolution
- Test service-to-service connectivity:
kubectl exec -n client-apps deployment/mcp-client-app -- curl http://weather-server-k8s.toolhive-system.svc.cluster.local:8080
ToolHive Operator issues:
- Check operator logs:
kubectl logs -n toolhive-system -l app.kubernetes.io/name=toolhive-operator
- Verify the operator is running:
kubectl get pods -n toolhive-system
Related information
- For conceptual understanding, see Authentication and authorization framework
- For detailed Cedar policy syntax, see Cedar policies and the Cedar documentation
- For running MCP servers without authentication, see Run MCP servers in Kubernetes
- For ToolHive Operator installation, see Deploy the ToolHive Operator with Helm