The ToolHive Registry API server supports OAuth 2.0/OIDC authentication to protect API endpoints.
- Overview
- Authentication Modes
- Multi-Provider Support
- Configuration
- Default Public Paths
- Provider Configuration
- RFC 9728 Support
- Examples
The server defaults to OAuth mode for security-by-default. For development environments, you can explicitly set mode: anonymous.
- OAuth 2.0 / OIDC token validation
- Multiple provider support with sequential fallback
- Kubernetes service account integration
- RFC 9728 Protected Resource Metadata
- Per-endpoint public path configuration
| Mode | Description | Use Case |
|---|---|---|
oauth |
OAuth 2.0/OIDC token validation | Production (default) |
anonymous |
No authentication required | Local development |
If no auth configuration is provided, the server defaults to OAuth mode. This ensures production deployments are secure by default.
To disable authentication for local development:
auth:
mode: anonymousThe server supports multiple OAuth providers with sequential fallback. This enables supporting both Kubernetes service accounts and external identity providers simultaneously.
When a token is received:
- The token is validated against each provider in order
- If validation succeeds with any provider, the request is authenticated
- If all providers fail, the request is rejected with 401 Unauthorized
auth:
mode: oauth
oauth:
resourceUrl: https://registry.example.com
providers:
- name: my-company-idp
issuerUrl: https://auth.company.com
audience: api://registry
- name: kubernetes
issuerUrl: https://kubernetes.default.svc
audience: https://kubernetes.default.svc
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crtThis configuration allows both:
- Employee access via company SSO
- Service-to-service access via Kubernetes service accounts
auth:
# Authentication mode: anonymous or oauth (default: oauth)
mode: oauth
# OAuth/OIDC configuration (required when mode is "oauth")
oauth:
# URL identifying this protected resource (RFC 9728)
resourceUrl: https://registry.example.com
# OAuth/OIDC providers (at least one required)
providers:
- name: my-idp
issuerUrl: https://idp.example.com
audience: api://registryauth:
mode: oauth
# Additional paths that bypass authentication (optional)
# These extend the default public paths listed below
publicPaths:
- /custom/public
- /metrics
# OAuth/OIDC configuration
oauth:
# URL identifying this protected resource (RFC 9728)
# Used in /.well-known/oauth-protected-resource endpoint
resourceUrl: https://registry.example.com
# Protection space identifier for WWW-Authenticate header (optional)
# Defaults to "mcp-registry"
realm: mcp-registry
# OAuth scopes supported by this resource (optional)
# Defaults to ["mcp-registry:read", "mcp-registry:write"]
scopesSupported:
- mcp-registry:read
- mcp-registry:write
- mcp-registry:admin
# OAuth/OIDC providers (at least one required)
providers:
- name: primary-idp
issuerUrl: https://idp.example.com
audience: api://registry
- name: kubernetes
issuerUrl: https://kubernetes.default.svc
audience: https://kubernetes.default.svc
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crtThe following endpoints are always accessible without authentication:
/health- Health check endpoint/readiness- Readiness probe endpoint/version- Version information/.well-known/*- OAuth discovery endpoints (RFC 9728)
Additional public paths can be configured using the publicPaths option.
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique identifier for this provider |
issuerUrl |
Yes | OIDC issuer URL (must be HTTPS in production) |
jwksUrl |
No | Direct JWKS URL (skips OIDC discovery if specified) |
audience |
Yes | Expected audience claim in the token |
clientId |
No | OAuth client ID for token introspection |
clientSecretFile |
No | Path to file containing client secret |
caCertPath |
No | Path to CA certificate for TLS verification |
authTokenFile |
No | Path to bearer token file for authenticating to OIDC/JWKS endpoints |
introspectionUrl |
No | Token introspection endpoint (RFC 7662) for opaque tokens |
allowPrivateIP |
No | Allow OIDC endpoints on private IP addresses (required for in-cluster Kubernetes) |
For Kubernetes service account tokens:
- name: kubernetes
issuerUrl: https://kubernetes.default.svc
jwksUrl: https://kubernetes.default.svc/openid/v1/jwks # Skip OIDC discovery
audience: https://kubernetes.default.svc
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
authTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token # Optional: for authenticated endpoints
allowPrivateIP: true # Required for in-cluster Kubernetes API serverNote: Using
jwksUrlis useful in Kubernetes where the OIDC discovery endpoint requires authentication. The JWKS endpoint is typically unauthenticated, but you can useauthTokenFileif authentication is required.
For external identity providers (Okta, Auth0, Keycloak, etc.):
- name: company-sso
issuerUrl: https://auth.company.com
audience: api://toolhive-registryFor providers with self-signed certificates:
- name: internal-idp
issuerUrl: https://internal-auth.company.local
audience: api://registry
caCertPath: /etc/ssl/certs/internal-ca.crtWhen OAuth is enabled, the server exposes an RFC 9728 compliant discovery endpoint:
GET /.well-known/oauth-protected-resource
{
"resource": "https://registry.example.com",
"authorization_servers": [
"https://idp.example.com",
"https://kubernetes.default.svc"
],
"bearer_methods_supported": ["header"],
"scopes_supported": [
"mcp-registry:read",
"mcp-registry:write"
]
}This endpoint allows OAuth clients to automatically discover authentication requirements.
auth:
mode: anonymousauth:
mode: oauth
oauth:
resourceUrl: https://registry.company.com
providers:
- name: company-sso
issuerUrl: https://auth.company.com
audience: api://toolhive-registryauth:
mode: oauth
oauth:
resourceUrl: https://registry.company.com
providers:
# Allow employee access via corporate SSO
- name: company-sso
issuerUrl: https://auth.company.com
audience: api://toolhive-registry
# Allow service-to-service access within cluster
- name: kubernetes
issuerUrl: https://kubernetes.default.svc
audience: https://kubernetes.default.svc
caCertPath: /var/run/secrets/kubernetes.io/serviceaccount/ca.crtauth:
mode: oauth
oauth:
resourceUrl: https://registry-us-west.company.com
providers:
- name: us-idp
issuerUrl: https://auth-us.company.com
audience: api://toolhive-registry
- name: eu-idp
issuerUrl: https://auth-eu.company.com
audience: api://toolhive-registry# Get token from your IDP (example)
TOKEN="your-jwt-token-here"
# Make authenticated request
curl -H "Authorization: Bearer $TOKEN" \
https://registry.example.com/registry/v0.1/servers# Get service account token
TOKEN=$(kubectl get secret <service-account-token> -o jsonpath='{.data.token}' | base64 -d)
# Make authenticated request
curl -H "Authorization: Bearer $TOKEN" \
https://registry.example.com/registry/v0.1/servers- Always use OAuth in production - Anonymous mode is for development only
- Use HTTPS for issuer URLs - Required in production environments
- Validate audience claims - Prevent token reuse across services
- Rotate secrets regularly - Use short-lived tokens when possible
- Limit scope to minimum required - Follow principle of least privilege
- Monitor authentication failures - Set up alerts for repeated failures
- Use separate providers - Don't share auth configuration across environments
Check:
- Token is not expired
- Token is in
Authorization: Bearer <token>header format - Issuer URL matches provider configuration
- Audience claim matches provider configuration
- Provider is reachable from the server
Check:
- Network connectivity to issuer URL
- CA certificate path is correct (for self-signed certs)
- DNS resolution for issuer hostname
- Firewall rules allow HTTPS traffic
Enable debug logging to see detailed error messages:
thv-registry-api serve --config config.yaml --debug- Configuration Guide - Complete configuration reference
- Kubernetes Deployment - Deploy with authentication
- Database Configuration - Database security considerations