Skip to main content

Authentication and authorization

This guide shows you how to secure your MCP servers in Kubernetes using authentication and authorization with the ToolHive Operator.

info

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:

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.

mcp-server-external-auth.yaml
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:

client-service-account.yaml
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:

mcp-server-k8s-auth.yaml
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:

client-app.yaml
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.

tip

For more policy examples and advanced usage, see Cedar policies.

Step 2: Create a ConfigMap with policies

Store your authorization configuration in a ConfigMap:

authz-configmap.yaml
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:

mcp-server-with-authz.yaml
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

  1. Deploy the external IdP configuration
  2. Obtain a valid JWT token from your identity provider
  3. Make a request to the MCP server including the token

Test service-to-service authentication

  1. Deploy both the MCP server and client application
  2. Check that the client can successfully call the MCP server
  3. Verify authentication in the ToolHive proxy logs:
kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s

Test authorization

  1. Make requests that should be permitted by your policies
  2. Make requests that should be denied
  3. Check the proxy logs to see authorization decisions

Troubleshooting

Authentication issues

If clients can't authenticate:

  1. Check that the JWT token is valid and not expired

  2. Verify that the audience and issuer match your configuration

  3. Ensure the JWKS URL is accessible

  4. Check the server logs for specific authentication errors:

    thv logs <server-name>

Authorization issues

If authenticated clients are denied access:

  1. Make sure your Cedar policies explicitly permit the specific action (remember, default deny)
  2. Check that the principal, action, and resource match what's in your policies (including case and formatting)
  3. Examine any conditions in your policies to ensure they're satisfied (for example, required JWT claims or tool arguments)
  4. 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