User Story
As an embedded auth server operator, I shouldn't have to configure jwksUrl pointing my MCPServer back at itself, or enable scary-sounding flags like insecureAllowHTTP or jwksAllowPrivateIP, just to get token validation working. The embedded auth server and the token validator are running in the same process — why is there an HTTP round-trip at all?
The Pain
Today, when you enable the embedded auth server on an MCPServer, OAuth flows work — tokens are issued successfully. But token validation silently fails because the token validator tries to fetch JWKS over HTTP from the proxy's own endpoint.
To make it work, you must set either jwksAllowPrivateIP: true (if the issuer resolves to a private IP) or insecureAllowHTTP: true (if the internal service URL is http://), or both. Neither is recommended for production, and the failures are silent — requiring deep debugging to diagnose.
The fundamental issue is that the embedded auth server's KeyProvider already holds the public keys in memory. The token validator doesn't need to fetch them over HTTP. The two components just don't talk to each other directly.
What Should Happen
When embedded auth is enabled, token validation should use the in-process KeyProvider directly. No jwksUrl, no HTTP fetch, no flags. The config should be as simple as:
oidcConfig:
type: inline
inline:
issuer: https://example.com
jwksSource: embedded
Or better yet, this should be the automatic default when the embedded auth server is active.
Affected Files
pkg/networking/http_client.go — ValidatingTransport.RoundTrip
pkg/auth/token.go — ensureJWKSRegistered, getKeyFromJWKS
pkg/runner/runner.go — runner wiring (pass KeyProvider to token validator)
cmd/thv-operator/api/v1alpha1/mcpserver_types.go — InlineOIDCConfig struct
Current Workarounds
Using localhost (only insecureAllowHTTP needed):
oidcConfig:
type: inline
inline:
issuer: https://example.com
jwksUrl: http://localhost:8080/.well-known/jwks.json
insecureAllowHTTP: true
Using the Kubernetes service name (insecureAllowHTTP + jwksAllowPrivateIP needed):
oidcConfig:
type: inline
inline:
issuer: https://example.com
jwksUrl: http://mcp-server-proxy.namespace.svc.cluster.local:8080/.well-known/jwks.json
jwksAllowPrivateIP: true
insecureAllowHTTP: true
The service name resolves to a cluster IP (private), so it needs both flags. Localhost only needs insecureAllowHTTP.
User Story
As an embedded auth server operator, I shouldn't have to configure
jwksUrlpointing my MCPServer back at itself, or enable scary-sounding flags likeinsecureAllowHTTPorjwksAllowPrivateIP, just to get token validation working. The embedded auth server and the token validator are running in the same process — why is there an HTTP round-trip at all?The Pain
Today, when you enable the embedded auth server on an MCPServer, OAuth flows work — tokens are issued successfully. But token validation silently fails because the token validator tries to fetch JWKS over HTTP from the proxy's own endpoint.
To make it work, you must set either
jwksAllowPrivateIP: true(if the issuer resolves to a private IP) orinsecureAllowHTTP: true(if the internal service URL ishttp://), or both. Neither is recommended for production, and the failures are silent — requiring deep debugging to diagnose.The fundamental issue is that the embedded auth server's
KeyProvideralready holds the public keys in memory. The token validator doesn't need to fetch them over HTTP. The two components just don't talk to each other directly.What Should Happen
When embedded auth is enabled, token validation should use the in-process
KeyProviderdirectly. NojwksUrl, no HTTP fetch, no flags. The config should be as simple as:Or better yet, this should be the automatic default when the embedded auth server is active.
Affected Files
pkg/networking/http_client.go—ValidatingTransport.RoundTrippkg/auth/token.go—ensureJWKSRegistered,getKeyFromJWKSpkg/runner/runner.go— runner wiring (passKeyProviderto token validator)cmd/thv-operator/api/v1alpha1/mcpserver_types.go—InlineOIDCConfigstructCurrent Workarounds
Using localhost (only
insecureAllowHTTPneeded):Using the Kubernetes service name (
insecureAllowHTTP+jwksAllowPrivateIPneeded):The service name resolves to a cluster IP (private), so it needs both flags. Localhost only needs
insecureAllowHTTP.