From bf3519a151fd11295a8da4ed828a2c0ffff692f4 Mon Sep 17 00:00:00 2001 From: Trey Date: Tue, 10 Feb 2026 08:54:44 -0800 Subject: [PATCH 1/6] Docs for embedded auth server --- docs/toolhive/concepts/auth-framework.mdx | 113 ++++++- docs/toolhive/guides-k8s/auth-k8s.mdx | 372 +++++++++++++++++++++- 2 files changed, 479 insertions(+), 6 deletions(-) diff --git a/docs/toolhive/concepts/auth-framework.mdx b/docs/toolhive/concepts/auth-framework.mdx index 0df6e5e3..b2159b22 100644 --- a/docs/toolhive/concepts/auth-framework.mdx +++ b/docs/toolhive/concepts/auth-framework.mdx @@ -55,6 +55,14 @@ server—just configure ToolHive with your IdP and write clear Cedar policies. This approach is more flexible, secure, and easier to manage for you and your team. +With the [embedded authorization server](#embedded-authorization-server), +ToolHive can also operate in a mode that aligns more closely with the MCP +specification's OAuth model. In this mode, the proxy exposes standard OAuth +endpoints (`/oauth/authorize`, `/oauth/token`, +`/.well-known/oauth-authorization-server`) and delegates authentication to an +upstream identity provider. This gives MCP clients a spec-compliant OAuth +experience while ToolHive still centralizes the complexity. + ## Authentication framework ToolHive uses OAuth-based authentication with support for both OAuth 2.1 and @@ -146,6 +154,104 @@ flowchart TD Cedar_Authorizer -->|Deny| Denied[403 Forbidden] ``` +### Embedded authorization server + +In the standard authentication flow described above, clients obtain tokens +independently from an external identity provider and present them to ToolHive +for validation. The embedded authorization server provides an alternative model +where ToolHive itself acts as an OAuth authorization server, retrieving tokens +from an upstream identity provider on behalf of clients. + +:::note + +The embedded authorization server is currently available only for Kubernetes +deployments using the ToolHive Operator. + +::: + +This approach is designed for MCP servers that accept `Authorization: Bearer` +tokens and is particularly useful when: + +- Your MCP clients support the MCP OAuth specification and expect OAuth + endpoints on the server (such as `/oauth/authorize` and `/oauth/token`) +- You want ToolHive to handle the full OAuth flow rather than requiring clients + to obtain tokens independently +- You need to authenticate interactive users through a corporate identity + provider like Okta or Microsoft Entra ID + +#### How the embedded authorization server works + +The embedded authorization server runs in-process within the ToolHive proxy. +When a client connects, the following flow occurs: + +1. The client is directed to the proxy's `/oauth/authorize` endpoint. +2. The proxy redirects the client to the upstream identity provider for + authentication. +3. The user authenticates with the upstream identity provider (for example, + signing in with Okta or Microsoft Entra ID). +4. The upstream identity provider redirects back to the proxy with an + authorization code. +5. The embedded authorization server exchanges the authorization code for tokens + with the upstream identity provider. +6. The embedded authorization server issues its own JWT to the client, signed + with keys you configure. +7. The client includes this JWT as a `Bearer` token in the `Authorization` + header on subsequent requests. +8. The proxy validates the JWT and forwards requests to the MCP server. + +```mermaid +sequenceDiagram + participant Client + participant Proxy as ToolHive Proxy + participant IdP as Upstream IdP + participant MCP as MCP Server + + Client->>Proxy: Connect to MCP server + Proxy-->>Client: Redirect to /oauth/authorize + Client->>Proxy: GET /oauth/authorize + Proxy-->>Client: Redirect to upstream IdP + Client->>IdP: Authenticate + IdP-->>Client: Redirect with authorization code + Client->>Proxy: GET /oauth/callback?code=... + Proxy->>IdP: Exchange code for tokens + IdP-->>Proxy: Upstream tokens + Proxy-->>Client: Issue ToolHive JWT + Client->>Proxy: MCP request with Bearer token + Proxy->>Proxy: Validate JWT + Proxy->>MCP: Forward request + MCP-->>Proxy: Response + Proxy-->>Client: Response +``` + +#### Key characteristics + +- **In-process execution:** The authorization server runs within the ToolHive + proxy—no separate infrastructure or sidecar containers needed. +- **Configurable signing keys:** JWTs are signed with keys you provide, + supporting key rotation for zero-downtime updates. +- **Flexible upstream providers:** Supports both OIDC providers (with automatic + endpoint discovery) and OAuth 2.0 providers (with explicit endpoint + configuration). +- **Configurable token lifespans:** Access tokens, refresh tokens, and + authorization codes have configurable durations with sensible defaults. +- **Single upstream provider:** Currently supports one upstream identity + provider per configuration. + +#### Comparing authentication models + +ToolHive supports two authentication models for different use cases: + +- **External token validation** (`oidcConfig` only): Clients obtain tokens + independently from an external identity provider and present them to ToolHive + for validation. This is best for service-to-service authentication or when + clients already manage their own OAuth flows. +- **Embedded authorization server** (`externalAuthConfigRef` + `oidcConfig`): + ToolHive runs its own OAuth authorization server, delegates user + authentication to an upstream identity provider, and issues JWTs that the + proxy validates via `oidcConfig` pointed at itself. This is best for + interactive user login flows or when MCP clients expect spec-compliant OAuth + endpoints. + ### Identity providers ToolHive can integrate with any provider that supports OAuth 2.1 or OIDC, @@ -158,8 +264,9 @@ including: - Auth0 - Kubernetes (service account tokens) -This flexibility lets you use your existing identity infrastructure for both -users and services, reducing operational overhead and improving security. +These same providers work with both external token validation and the embedded +authorization server. For the embedded authorization server, the upstream +provider must support the OAuth 2.0 authorization code flow. ### Token validation methods @@ -280,6 +387,8 @@ standardized across clients. ## Related information +- For configuring the embedded authorization server in Kubernetes, see + [Embedded authorization server authentication](../guides-k8s/auth-k8s.mdx#set-up-embedded-authorization-server-authentication) - For backend authentication concepts, see [Backend authentication](./backend-auth.mdx) - For detailed policy writing guidance, see diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index 9535a141..8125eb0a 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -33,8 +33,7 @@ You'll need: ## Choose your authentication approach -There are three main ways to authenticate with MCP servers running in -Kubernetes: +There are four main ways to authenticate with MCP servers running in Kubernetes: ### Approach 1: External identity provider authentication @@ -67,6 +66,24 @@ account tokens for authentication. - Client applications running in Kubernetes pods - Understanding of Kubernetes service accounts and RBAC +### Approach 4: Embedded authorization server authentication + +Use this when your MCP clients support the MCP OAuth specification and you want +ToolHive to handle the full OAuth flow, including redirecting users to an +upstream identity provider for authentication. This approach is ideal when you +need ToolHive to retrieve OAuth tokens from an upstream provider for MCP servers +that accept `Authorization: Bearer` tokens. + +For conceptual background, see +[Embedded authorization server](../concepts/auth-framework.mdx#embedded-authorization-server). + +**Prerequisites for embedded authorization server:** + +- An upstream identity provider that supports the OAuth 2.0 authorization code + flow (such as Okta, Microsoft Entra ID, Auth0, or any OIDC-compliant provider) +- A registered OAuth application/client with your upstream provider +- Client ID and client secret from your upstream provider + ## Set up external identity provider authentication **Step 1: Create an MCPServer with external OIDC** @@ -304,10 +321,281 @@ 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 embedded authorization server authentication + +The embedded authorization server runs an OAuth authorization server within the +ToolHive proxy. It handles the full OAuth flow by redirecting users to your +upstream identity provider for authentication, then issuing JWTs that the proxy +validates on subsequent requests. This provides MCP servers with +`Authorization: Bearer` tokens without requiring separate authorization server +infrastructure. + +This setup uses the `MCPExternalAuthConfig` custom resource, following the same +pattern as [token exchange configuration](./token-exchange-k8s.mdx). + +**Step 1: Create a Secret for the upstream provider client credentials** + +Store the OAuth client secret for your upstream identity provider: + +```yaml title="upstream-idp-secret.yaml" +apiVersion: v1 +kind: Secret +metadata: + name: upstream-idp-secret + namespace: toolhive-system +type: Opaque +stringData: + client-secret: '' +``` + +```bash +kubectl apply -f upstream-idp-secret.yaml +``` + +**Step 2: Create a Secret for JWT signing keys** + +The embedded authorization server signs JWTs with a private key you provide. +Generate a PEM-encoded private key (RSA or EC), for example: + +```bash +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out signing-key.pem +``` + +Then create a Secret containing the key: + +```yaml title="auth-server-signing-key.yaml" +apiVersion: v1 +kind: Secret +metadata: + name: auth-server-signing-key + namespace: toolhive-system +type: Opaque +stringData: + signing-key: | + -----BEGIN EC PRIVATE KEY----- + + -----END EC PRIVATE KEY----- +``` + +```bash +kubectl apply -f auth-server-signing-key.yaml +``` + +:::tip[Key rotation] + +For key rotation, you can reference multiple signing key Secrets in the +`signingKeySecretRefs` list. The first key is used for signing new tokens. +Additional keys are used for verification only, so tokens signed before rotation +remain valid. + +::: + +**Step 3: Create a Secret for HMAC keys** + +The embedded authorization server uses a symmetric HMAC key to sign +authorization codes and refresh tokens. The key must be at least 32 bytes and +cryptographically random: + +```yaml title="auth-server-hmac-secret.yaml" +apiVersion: v1 +kind: Secret +metadata: + name: auth-server-hmac-secret + namespace: toolhive-system +type: Opaque +stringData: + hmac-key: '' +``` + +```bash +kubectl apply -f auth-server-hmac-secret.yaml +``` + +:::warning[Ephemeral keys for development only] + +If you omit the `signingKeySecretRefs` and `hmacSecretRefs` fields, ToolHive +generates ephemeral keys that are lost on pod restart. All previously issued +tokens become invalid after a restart. Only omit these Secrets for development +and testing. + +::: + +**Step 4: Create the MCPExternalAuthConfig resource** + +Create an `MCPExternalAuthConfig` resource with the `embeddedAuthServer` type. +This example configures an OIDC upstream provider (the most common case): + +```yaml title="embedded-auth-config.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPExternalAuthConfig +metadata: + name: embedded-auth-server + namespace: toolhive-system +spec: + type: embeddedAuthServer + embeddedAuthServer: + issuer: 'https://mcp.example.com' + signingKeySecretRefs: + - name: auth-server-signing-key + key: signing-key + hmacSecretRefs: + - name: auth-server-hmac-secret + key: hmac-key + tokenLifespans: + accessTokenLifespan: '1h' + refreshTokenLifespan: '168h' + authCodeLifespan: '10m' + upstreamProviders: + - name: okta + type: oidc + oidcConfig: + issuerUrl: 'https://dev-123456.okta.com/oauth2/default' + clientId: '' + clientSecretRef: + name: upstream-idp-secret + key: client-secret + scopes: + - openid + - offline_access + - profile + - email +``` + +```bash +kubectl apply -f embedded-auth-config.yaml +``` + +**Configuration reference:** + +| Field | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `issuer` | HTTPS URL identifying this authorization server. Appears in the `iss` claim of issued JWTs. | +| `signingKeySecretRefs` | References to Secrets containing JWT signing keys. First key is active; additional keys support rotation. | +| `hmacSecretRefs` | References to Secrets with symmetric keys for signing authorization codes and refresh tokens. | +| `tokenLifespans` | Configurable durations for access tokens (default: 1h), refresh tokens (default: 168h), and auth codes (default: 10m). | +| `upstreamProviders` | Configuration for the upstream identity provider. Currently supports one provider. | + +**Step 5: Create the MCPServer resource** + +Create an `MCPServer` resource that references the embedded authorization server +configuration. The MCPServer uses two fields together: + +- `externalAuthConfigRef`: references the embedded authorization server + configuration you created in step 4 +- `oidcConfig`: validates JWTs issued by the embedded authorization server, with + the issuer pointed at the embedded authorization server itself + +```yaml title="mcp-server-embedded-auth.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPServer +metadata: + name: weather-server-embedded + namespace: toolhive-system +spec: + image: ghcr.io/stackloklabs/weather-mcp/server + transport: streamable-http + port: 8080 + permissionProfile: + type: builtin + name: network + # Reference the embedded authorization server configuration + externalAuthConfigRef: + name: embedded-auth-server + # Validate JWTs issued by the embedded authorization server + oidcConfig: + type: inline + resourceUrl: 'https://mcp.example.com' + inline: + issuer: 'https://mcp.example.com' + resources: + limits: + cpu: '100m' + memory: '128Mi' + requests: + cpu: '50m' + memory: '64Mi' +``` + +```bash +kubectl apply -f mcp-server-embedded-auth.yaml +``` + +:::note + +The `oidcConfig` issuer must match the `issuer` in your `MCPExternalAuthConfig`. +The embedded authorization server exposes a JWKS endpoint that the proxy uses to +validate the JWTs it issues. The proxy also exposes OAuth discovery endpoints +(`/.well-known/oauth-authorization-server`) so MCP clients can discover the +authorization endpoints automatically. + +::: + +### Using an OAuth 2.0 upstream provider + +If your upstream identity provider does not support OIDC discovery, you can +configure it as an OAuth 2.0 provider with explicit endpoints. This is useful +for providers like GitHub that use OAuth 2.0 but don't implement the full OIDC +specification. + +```yaml title="embedded-auth-oauth2-config.yaml" +apiVersion: toolhive.stacklok.dev/v1alpha1 +kind: MCPExternalAuthConfig +metadata: + name: embedded-auth-oauth2 + namespace: toolhive-system +spec: + type: embeddedAuthServer + embeddedAuthServer: + issuer: 'https://mcp.example.com' + signingKeySecretRefs: + - name: auth-server-signing-key + key: signing-key + hmacSecretRefs: + - name: auth-server-hmac-secret + key: hmac-key + upstreamProviders: + - name: github + type: oauth2 + oauth2Config: + authorizationEndpoint: 'https://github.com/login/oauth/authorize' + tokenEndpoint: 'https://github.com/login/oauth/access_token' + userInfo: + endpointUrl: 'https://api.github.com/user' + httpMethod: GET + additionalHeaders: + Accept: 'application/vnd.github+json' + fieldMapping: + subjectFields: + - id + - login + nameFields: + - name + - login + emailFields: + - email + clientId: '' + clientSecretRef: + name: upstream-idp-secret + key: client-secret + scopes: + - user:email + - read:user +``` + +:::note + +OAuth 2.0 providers require explicit endpoint configuration and a `userInfo` +section, unlike OIDC providers which auto-discover these from the issuer URL. +The `fieldMapping` section maps provider-specific response fields to standard +user identity fields. For example, GitHub returns `login` instead of the +standard `name` field. + +::: + ## Set up authorization -Both authentication approaches can use the same authorization configuration -using Cedar policies. +All authentication approaches can use the same authorization configuration using +Cedar policies. **Step 1: Create authorization configuration** @@ -406,6 +694,32 @@ kubectl apply -f mcp-server-with-authz.yaml kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s ``` +### Test embedded authorization server authentication + +1. Deploy the `MCPExternalAuthConfig` and `MCPServer` resources +2. Check that the MCPServer is running: + + ```bash + kubectl get mcpserver -n toolhive-system weather-server-embedded + ``` + +3. If the server is exposed outside the cluster, verify the OAuth discovery + endpoint is available: + + ```bash + curl https:///.well-known/oauth-authorization-server + ``` + +4. Connect with an MCP client that supports the MCP OAuth specification. The + client should be redirected to your upstream identity provider for + authentication. +5. Check the proxy logs for successful authentication: + + ```bash + kubectl logs -n toolhive-system \ + -l app.kubernetes.io/name=weather-server-embedded + ``` + ### Test authorization 1. Make requests that should be permitted by your policies @@ -416,6 +730,10 @@ kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s - For conceptual understanding, see [Authentication and authorization framework](../concepts/auth-framework.mdx) +- For conceptual background on the embedded authorization server, see + [Embedded authorization server](../concepts/auth-framework.mdx#embedded-authorization-server) +- For a similar configuration pattern using token exchange, see + [Configure token exchange](./token-exchange-k8s.mdx) - For detailed Cedar policy syntax, see [Cedar policies](../concepts/cedar-policies.mdx) and the [Cedar documentation](https://docs.cedarpolicy.com/) @@ -469,3 +787,49 @@ kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s - Verify the operator is running: `kubectl get pods -n toolhive-system` + +
+Embedded authorization server issues + +**OAuth flow not initiating:** + +- Verify the `MCPExternalAuthConfig` resource exists in the same namespace: + `kubectl get mcpexternalauthconfig -n toolhive-system` +- Check that the `externalAuthConfigRef.name` in your `MCPServer` matches the + `MCPExternalAuthConfig` resource name +- Verify the upstream provider's client ID and redirect URI are correctly + configured in the `MCPExternalAuthConfig` + +**Token validation failures after restart:** + +- Ensure you have configured `signingKeySecretRefs` and `hmacSecretRefs` with + persistent keys +- Without these, ephemeral keys are generated on startup, invalidating all + previously issued tokens + +**Upstream IdP redirect errors:** + +- Verify the redirect URI configured in your upstream provider matches the + ToolHive proxy's callback URL (typically + `https:///oauth/callback`) +- Check that the upstream provider's issuer URL is accessible from within the + cluster +- For OIDC providers, ensure the `/.well-known/openid-configuration` endpoint is + reachable from the proxy pod + +**JWT signing key issues:** + +- Verify signing key Secrets exist: + `kubectl get secret -n toolhive-system auth-server-signing-key` +- Ensure the key format is correct (PEM-encoded RSA or EC private key) +- Check proxy logs for key loading errors: + `kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-embedded` + +**OIDC configuration mismatch:** + +- Ensure the `oidcConfig.inline.issuer` on your `MCPServer` matches the `issuer` + in your `MCPExternalAuthConfig` +- Verify the `resourceUrl` in `oidcConfig` matches the external URL of the MCP + server + +
From d3b760f1f1f0b1e6e53ca720523fed5db50992b5 Mon Sep 17 00:00:00 2001 From: Trey Date: Tue, 10 Feb 2026 09:04:12 -0800 Subject: [PATCH 2/6] Add example HMAC key generation --- docs/toolhive/guides-k8s/auth-k8s.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index 8125eb0a..6ed1193e 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -394,7 +394,11 @@ remain valid. The embedded authorization server uses a symmetric HMAC key to sign authorization codes and refresh tokens. The key must be at least 32 bytes and -cryptographically random: +cryptographically random, for example: + +```bash +openssl rand -base64 32 +``` ```yaml title="auth-server-hmac-secret.yaml" apiVersion: v1 From e03d394443eb565c0a2ff389cb2911a99bd02c29 Mon Sep 17 00:00:00 2001 From: Trey Date: Wed, 11 Feb 2026 08:01:05 -0800 Subject: [PATCH 3/6] Address feedback --- docs/toolhive/concepts/auth-framework.mdx | 92 +++++++++++++++-------- docs/toolhive/guides-k8s/auth-k8s.mdx | 4 +- 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/docs/toolhive/concepts/auth-framework.mdx b/docs/toolhive/concepts/auth-framework.mdx index b2159b22..ce128484 100644 --- a/docs/toolhive/concepts/auth-framework.mdx +++ b/docs/toolhive/concepts/auth-framework.mdx @@ -41,27 +41,40 @@ flexible, and auditable. You don't need to add custom authentication or authorization logic to every server—ToolHive handles it for you, consistently and securely. -## ToolHive vs. MCP specification +## Why ToolHive centralizes authentication The -[official Model Context Protocol (MCP) specification](https://modelcontextprotocol.io/docs/tutorials/security/authorization) -recommends OAuth 2.1-based authorization for HTTP transports, which requires -each MCP server to act as an OAuth resource server that validates access tokens -and enforces scope-based access control. ToolHive takes a different approach: it -centralizes authentication and authorization in its proxy layer, using -OAuth/OIDC for authentication and Cedar for fine-grained authorization. This -means you don't need to implement token validation or scope management in every -server—just configure ToolHive with your IdP and write clear Cedar policies. -This approach is more flexible, secure, and easier to manage for you and your -team. +[official MCP specification](https://modelcontextprotocol.io/docs/tutorials/security/authorization) +recommends OAuth 2.1-based authorization for HTTP transports, where each MCP +server acts as an OAuth resource server. In practice, this model creates +significant operational challenges: + +- **OAuth client registration burden:** OAuth 2.0 requires pre-registered + redirect URIs at each identity provider. Many providers—such as Google, + GitHub, and Atlassian—require manual registration of OAuth clients to obtain a + client ID and client secret. If each user client (for example, an IDE) were + its own OAuth client, the registration burden would be impractical at scale. +- **No federation with external services:** While token exchange (RFC 8693) and + federated identity providers work when the upstream service is in the same + trust domain, many MCP servers need to access external services like GitHub, + Google, or Atlassian APIs where no federation relationship exists. +- **Per-server implementation cost:** Each MCP server would need to implement + its own token validation and scope management, duplicating security-critical + logic across servers. + +ToolHive addresses these challenges by centralizing authentication and +authorization in its proxy layer. You configure ToolHive with your identity +provider and write Cedar policies for fine-grained authorization—individual MCP +servers don't need to implement token validation or scope management. With the [embedded authorization server](#embedded-authorization-server), -ToolHive can also operate in a mode that aligns more closely with the MCP -specification's OAuth model. In this mode, the proxy exposes standard OAuth -endpoints (`/oauth/authorize`, `/oauth/token`, -`/.well-known/oauth-authorization-server`) and delegates authentication to an -upstream identity provider. This gives MCP clients a spec-compliant OAuth -experience while ToolHive still centralizes the complexity. +ToolHive can also operate in a mode that aligns with the MCP specification's +OAuth model. The proxy exposes standard OAuth endpoints (`/oauth/authorize`, +`/oauth/token`, `/.well-known/oauth-authorization-server`) and handles the full +OAuth web flow—clients don't need to obtain or manage tokens externally. +ToolHive delegates authentication to an upstream identity provider and issues +its own tokens, giving MCP clients a spec-compliant OAuth experience while +centralizing the complexity of client registration and token management. ## Authentication framework @@ -172,8 +185,6 @@ deployments using the ToolHive Operator. This approach is designed for MCP servers that accept `Authorization: Bearer` tokens and is particularly useful when: -- Your MCP clients support the MCP OAuth specification and expect OAuth - endpoints on the server (such as `/oauth/authorize` and `/oauth/token`) - You want ToolHive to handle the full OAuth flow rather than requiring clients to obtain tokens independently - You need to authenticate interactive users through a corporate identity @@ -234,23 +245,38 @@ sequenceDiagram configuration). - **Configurable token lifespans:** Access tokens, refresh tokens, and authorization codes have configurable durations with sensible defaults. +- **Dynamic Client Registration (DCR):** Supports OAuth 2.0 Dynamic Client + Registration (RFC 7591), allowing MCP clients to register automatically + without manual configuration at the identity provider. +- **Direct upstream redirect:** The embedded authorization server redirects + clients directly to the upstream provider for authentication. This means the + upstream provider must be the service whose API the MCP server calls (for + example, GitHub or Atlassian). Chained authentication—where a client + authenticates with a corporate IdP like Okta, which then federates to an + external provider like GitHub—is not yet supported. - **Single upstream provider:** Currently supports one upstream identity provider per configuration. -#### Comparing authentication models - -ToolHive supports two authentication models for different use cases: - -- **External token validation** (`oidcConfig` only): Clients obtain tokens - independently from an external identity provider and present them to ToolHive - for validation. This is best for service-to-service authentication or when - clients already manage their own OAuth flows. -- **Embedded authorization server** (`externalAuthConfigRef` + `oidcConfig`): - ToolHive runs its own OAuth authorization server, delegates user - authentication to an upstream identity provider, and issues JWTs that the - proxy validates via `oidcConfig` pointed at itself. This is best for - interactive user login flows or when MCP clients expect spec-compliant OAuth - endpoints. +#### Choosing the right backend authentication model + +How you configure backend authentication depends on what the MCP server needs to +call and how that backend service accepts credentials: + +- **Static credentials or API keys:** If the MCP server only supports static + credentials or API keys, configure them in ToolHive directly—either as + environment variables, secrets, or injected headers. No token exchange or + embedded authorization server is needed. +- **Token exchange:** If the MCP server makes authenticated API calls to a + backend service in the same trust domain as your corporate identity provider + (for example, an internal API that accepts tokens from your Okta or Entra ID + tenant), token exchange is a good fit. ToolHive exchanges the client's token + for a backend-scoped token using RFC 8693, preserving the user's identity + across services. See [Backend authentication](./backend-auth.mdx) for details. +- **Embedded authorization server:** If the MCP server needs to call an external + API where no federation relationship exists—such as GitHub, Google, or + Atlassian APIs—the embedded authorization server is a good fit. It runs the + full OAuth web flow against the external provider, obtaining tokens that the + MCP server can use to access those APIs on behalf of the user. ### Identity providers diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index 6ed1193e..bf7f2570 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -372,9 +372,9 @@ metadata: type: Opaque stringData: signing-key: | - -----BEGIN EC PRIVATE KEY----- + -----BEGIN PRIVATE KEY----- - -----END EC PRIVATE KEY----- + -----END PRIVATE KEY----- ``` ```bash From 161bd053af6c8d976493dc38919d48cef64a45e7 Mon Sep 17 00:00:00 2001 From: Trey Date: Wed, 11 Feb 2026 08:10:29 -0800 Subject: [PATCH 4/6] Update auth-framework.mdx --- docs/toolhive/concepts/auth-framework.mdx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/toolhive/concepts/auth-framework.mdx b/docs/toolhive/concepts/auth-framework.mdx index ce128484..79b88392 100644 --- a/docs/toolhive/concepts/auth-framework.mdx +++ b/docs/toolhive/concepts/auth-framework.mdx @@ -183,12 +183,8 @@ deployments using the ToolHive Operator. ::: This approach is designed for MCP servers that accept `Authorization: Bearer` -tokens and is particularly useful when: - -- You want ToolHive to handle the full OAuth flow rather than requiring clients - to obtain tokens independently -- You need to authenticate interactive users through a corporate identity - provider like Okta or Microsoft Entra ID +tokens and is particularly useful when you want ToolHive to handle the full +OAuth flow rather than requiring clients to obtain tokens independently #### How the embedded authorization server works From 84f3fe95379d54f55061488a2fe917ac20dc25b1 Mon Sep 17 00:00:00 2001 From: Trey Date: Thu, 12 Feb 2026 08:57:57 -0800 Subject: [PATCH 5/6] Address feedback --- docs/toolhive/concepts/auth-framework.mdx | 85 +++++++++++------------ docs/toolhive/concepts/backend-auth.mdx | 25 +++++++ docs/toolhive/guides-k8s/auth-k8s.mdx | 34 +++++---- 3 files changed, 82 insertions(+), 62 deletions(-) diff --git a/docs/toolhive/concepts/auth-framework.mdx b/docs/toolhive/concepts/auth-framework.mdx index 79b88392..63ed4e1d 100644 --- a/docs/toolhive/concepts/auth-framework.mdx +++ b/docs/toolhive/concepts/auth-framework.mdx @@ -56,8 +56,9 @@ significant operational challenges: its own OAuth client, the registration burden would be impractical at scale. - **No federation with external services:** While token exchange (RFC 8693) and federated identity providers work when the upstream service is in the same - trust domain, many MCP servers need to access external services like GitHub, - Google, or Atlassian APIs where no federation relationship exists. + trust domain as the MCP server or has an established trust relationship with + the identity provider, many MCP servers need to access external services like + GitHub, Google, or Atlassian APIs where no federation relationship exists. - **Per-server implementation cost:** Each MCP server would need to implement its own token validation and scope management, duplicating security-critical logic across servers. @@ -68,13 +69,12 @@ provider and write Cedar policies for fine-grained authorization—individual MC servers don't need to implement token validation or scope management. With the [embedded authorization server](#embedded-authorization-server), -ToolHive can also operate in a mode that aligns with the MCP specification's -OAuth model. The proxy exposes standard OAuth endpoints (`/oauth/authorize`, -`/oauth/token`, `/.well-known/oauth-authorization-server`) and handles the full -OAuth web flow—clients don't need to obtain or manage tokens externally. -ToolHive delegates authentication to an upstream identity provider and issues -its own tokens, giving MCP clients a spec-compliant OAuth experience while -centralizing the complexity of client registration and token management. +ToolHive can also manage interactive token acquisition. The proxy exposes +standard OAuth endpoints and handles the full OAuth web flow—clients don't need +to obtain or manage tokens externally. ToolHive delegates authentication to an +upstream identity provider and issues its own tokens, giving MCP clients a +spec-compliant OAuth experience while centralizing the complexity of client +registration and token management. ## Authentication framework @@ -184,27 +184,30 @@ deployments using the ToolHive Operator. This approach is designed for MCP servers that accept `Authorization: Bearer` tokens and is particularly useful when you want ToolHive to handle the full -OAuth flow rather than requiring clients to obtain tokens independently +OAuth flow rather than requiring clients to obtain tokens independently. #### How the embedded authorization server works The embedded authorization server runs in-process within the ToolHive proxy. When a client connects, the following flow occurs: -1. The client is directed to the proxy's `/oauth/authorize` endpoint. -2. The proxy redirects the client to the upstream identity provider for +1. If the client is not yet registered, it registers via Dynamic Client + Registration (DCR), receiving a `client_id` and `client_secret`. +2. The client is directed to the ToolHive authorization endpoint. +3. The proxy redirects the client to the upstream identity provider for authentication. -3. The user authenticates with the upstream identity provider (for example, - signing in with Okta or Microsoft Entra ID). -4. The upstream identity provider redirects back to the proxy with an +4. The user authenticates with the upstream identity provider (for example, + signing in with Google or GitHub). +5. The upstream identity provider redirects back to the proxy with an authorization code. -5. The embedded authorization server exchanges the authorization code for tokens +6. The embedded authorization server exchanges the authorization code for tokens with the upstream identity provider. -6. The embedded authorization server issues its own JWT to the client, signed +7. The embedded authorization server issues its own JWT to the client, signed with keys you configure. -7. The client includes this JWT as a `Bearer` token in the `Authorization` +8. The client includes this JWT as a `Bearer` token in the `Authorization` header on subsequent requests. -8. The proxy validates the JWT and forwards requests to the MCP server. +9. The proxy validates the JWT, retrieves the upstream token, and forwards + requests to the MCP server. ```mermaid sequenceDiagram @@ -213,6 +216,8 @@ sequenceDiagram participant IdP as Upstream IdP participant MCP as MCP Server + Client->>Proxy: POST /oauth/register (DCR) + Proxy-->>Client: client_id + client_secret Client->>Proxy: Connect to MCP server Proxy-->>Client: Redirect to /oauth/authorize Client->>Proxy: GET /oauth/authorize @@ -245,34 +250,26 @@ sequenceDiagram Registration (RFC 7591), allowing MCP clients to register automatically without manual configuration at the identity provider. - **Direct upstream redirect:** The embedded authorization server redirects - clients directly to the upstream provider for authentication. This means the - upstream provider must be the service whose API the MCP server calls (for - example, GitHub or Atlassian). Chained authentication—where a client - authenticates with a corporate IdP like Okta, which then federates to an - external provider like GitHub—is not yet supported. + clients directly to the upstream provider for authentication (for example, + GitHub or Atlassian). - **Single upstream provider:** Currently supports one upstream identity provider per configuration. -#### Choosing the right backend authentication model - -How you configure backend authentication depends on what the MCP server needs to -call and how that backend service accepts credentials: - -- **Static credentials or API keys:** If the MCP server only supports static - credentials or API keys, configure them in ToolHive directly—either as - environment variables, secrets, or injected headers. No token exchange or - embedded authorization server is needed. -- **Token exchange:** If the MCP server makes authenticated API calls to a - backend service in the same trust domain as your corporate identity provider - (for example, an internal API that accepts tokens from your Okta or Entra ID - tenant), token exchange is a good fit. ToolHive exchanges the client's token - for a backend-scoped token using RFC 8693, preserving the user's identity - across services. See [Backend authentication](./backend-auth.mdx) for details. -- **Embedded authorization server:** If the MCP server needs to call an external - API where no federation relationship exists—such as GitHub, Google, or - Atlassian APIs—the embedded authorization server is a good fit. It runs the - full OAuth web flow against the external provider, obtaining tokens that the - MCP server can use to access those APIs on behalf of the user. +:::info[Chained authentication not yet supported] + +The embedded authorization server redirects clients directly to the upstream +provider. This means the upstream provider must be the service whose API the MCP +server calls. Chained authentication—where a client authenticates with a +corporate IdP like Okta, which then federates to an external provider like +GitHub—is not yet supported. If your deployment requires this pattern, consider +using [token exchange](./backend-auth.mdx#same-idp-with-token-exchange) with a +federated identity provider instead. + +::: + +For guidance on choosing the right backend authentication pattern for your MCP +servers, see +[Choosing the right backend authentication model](./backend-auth.mdx#choosing-the-right-backend-authentication-model). ### Identity providers diff --git a/docs/toolhive/concepts/backend-auth.mdx b/docs/toolhive/concepts/backend-auth.mdx index 3274e09c..359f31c3 100644 --- a/docs/toolhive/concepts/backend-auth.mdx +++ b/docs/toolhive/concepts/backend-auth.mdx @@ -283,8 +283,33 @@ ToolHive's token exchange approach provides several key advantages: - **Consistent:** The same pattern works across different backend services and identity providers +## Choosing the right backend authentication model + +How you configure backend authentication depends on what the MCP server needs to +call and how that backend service accepts credentials: + +- **Static credentials or API keys:** If the MCP server only supports static + credentials or API keys, configure them in ToolHive directly—either as + environment variables, secrets, or injected headers. No token exchange or + embedded authorization server is needed. +- **Token exchange:** If the MCP server makes authenticated API calls to a + backend service in the same trust domain as your corporate identity provider + (for example, an internal API that accepts tokens from your Okta or Entra ID + tenant), or federation exists between the two, token exchange is a good fit. + ToolHive exchanges the client's token for a backend-scoped token using RFC + 8693, preserving the user's identity across services. +- **Embedded authorization server:** If the MCP server needs to call an external + API where no federation relationship exists—such as GitHub, Google, or + Atlassian APIs—the + [embedded authorization server](./auth-framework.mdx#embedded-authorization-server) + is a good fit. It runs the full OAuth web flow against the external provider, + obtaining tokens that the MCP server can use to access those APIs on behalf of + the user. + ## Related information - For client authentication concepts, see [Authentication and authorization](./auth-framework.mdx) +- For the embedded authorization server, see + [Embedded authorization server](./auth-framework.mdx#embedded-authorization-server) - For policy configuration, see [Cedar policies](./cedar-policies.mdx) diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index bf7f2570..7eae2ddf 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -68,11 +68,10 @@ account tokens for authentication. ### Approach 4: Embedded authorization server authentication -Use this when your MCP clients support the MCP OAuth specification and you want -ToolHive to handle the full OAuth flow, including redirecting users to an -upstream identity provider for authentication. This approach is ideal when you -need ToolHive to retrieve OAuth tokens from an upstream provider for MCP servers -that accept `Authorization: Bearer` tokens. +Use this when you want ToolHive to handle the full OAuth flow, including +redirecting users to an upstream identity provider for authentication. This +approach is ideal when you need ToolHive to retrieve OAuth tokens from an +upstream provider for MCP servers that accept `Authorization: Bearer` tokens. For conceptual background, see [Embedded authorization server](../concepts/auth-framework.mdx#embedded-authorization-server). @@ -450,17 +449,16 @@ spec: refreshTokenLifespan: '168h' authCodeLifespan: '10m' upstreamProviders: - - name: okta + - name: google type: oidc oidcConfig: - issuerUrl: 'https://dev-123456.okta.com/oauth2/default' - clientId: '' + issuerUrl: 'https://accounts.google.com' + clientId: '' clientSecretRef: name: upstream-idp-secret key: client-secret scopes: - openid - - offline_access - profile - email ``` @@ -481,13 +479,12 @@ kubectl apply -f embedded-auth-config.yaml **Step 5: Create the MCPServer resource** -Create an `MCPServer` resource that references the embedded authorization server -configuration. The MCPServer uses two fields together: - -- `externalAuthConfigRef`: references the embedded authorization server - configuration you created in step 4 -- `oidcConfig`: validates JWTs issued by the embedded authorization server, with - the issuer pointed at the embedded authorization server itself +The MCPServer needs two configuration references: `externalAuthConfigRef` +enables the embedded authorization server, and `oidcConfig` validates the JWTs +that the embedded authorization server issues. Unlike approaches 1–3 where +`oidcConfig` points to an external identity provider, here it points to the +embedded authorization server itself—the `oidcConfig` issuer must match the +`issuer` in your `MCPExternalAuthConfig`. ```yaml title="mcp-server-embedded-auth.yaml" apiVersion: toolhive.stacklok.dev/v1alpha1 @@ -498,7 +495,7 @@ metadata: spec: image: ghcr.io/stackloklabs/weather-mcp/server transport: streamable-http - port: 8080 + proxyPort: 8080 permissionProfile: type: builtin name: network @@ -508,8 +505,9 @@ spec: # Validate JWTs issued by the embedded authorization server oidcConfig: type: inline - resourceUrl: 'https://mcp.example.com' + resourceUrl: 'https://mcp.example.com/mcp' inline: + # This must match the embedded authorization server issuer url issuer: 'https://mcp.example.com' resources: limits: From 90cfa2fa65da8667cbe73d0b06562fc7dc9a62ac Mon Sep 17 00:00:00 2001 From: Trey Date: Thu, 12 Feb 2026 08:59:57 -0800 Subject: [PATCH 6/6] Update auth-k8s.mdx --- docs/toolhive/guides-k8s/auth-k8s.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index 7eae2ddf..18f40bbb 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -70,8 +70,7 @@ account tokens for authentication. Use this when you want ToolHive to handle the full OAuth flow, including redirecting users to an upstream identity provider for authentication. This -approach is ideal when you need ToolHive to retrieve OAuth tokens from an -upstream provider for MCP servers that accept `Authorization: Bearer` tokens. +approach is ideal for MCP servers that accept `Authorization: Bearer` tokens. For conceptual background, see [Embedded authorization server](../concepts/auth-framework.mdx#embedded-authorization-server).