oauth

package
v0.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package oauth implements a generic, metadata-driven OAuth 2.0 credential manager for remote MCP servers. OAuth-only MCP servers (e.g. Slack's public MCP at https://mcp.slack.com/mcp) reject hand-pasted tokens with -32001 invalid_token because they only trust a bearer access token minted by their own OAuth flow. The model-router can only present a static bearer read from a mounted Secret, so this component obtains and refreshes OAuth access tokens (rfc8414 discovery + authorization_code/refresh_token grants) and writes them into the Secret that the MCPServer's auth.bearerToken already points at. The same logic is reused for any MCP server that publishes OAuth metadata — Slack is merely the first consumer.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RequeueInterval

func RequeueInterval(resp *TokenResponse, refreshInterval time.Duration) time.Duration

RequeueInterval returns how long after which the controller/oauth-proxy should refresh again: cfg.RefreshInterval when set, else 80% of the token's lifetime when expires_in is known, else a 50m default (Slack omits expires_in).

Types

type AuthorizationServerMetadata

type AuthorizationServerMetadata struct {
	Issuer                            string   `json:"issuer"`
	AuthorizationEndpoint             string   `json:"authorization_endpoint"`
	TokenEndpoint                     string   `json:"token_endpoint"`
	GrantTypesSupported               []string `json:"grant_types_supported"`
	TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
	CodeChallengeMethodsSupported     []string `json:"code_challenge_methods_supported"`
	ScopesSupported                   []string `json:"scopes_supported"`
	ResponseTypesSupported            []string `json:"response_types_supported"`
}

AuthorizationServerMetadata is the response of /.well-known/oauth-authorization-server (RFC 8414).

type Config

type Config struct {
	// ServerURL is the MCP server URL (e.g. https://mcp.slack.com/mcp). The issuer
	// origin is derived from it for metadata discovery.
	ServerURL string
	// ClientID / ClientSecret are the OAuth client credentials (Slack app credentials).
	ClientID     string
	ClientSecret string
	// RedirectURI is the OAuth redirect URI registered in the app.
	RedirectURI string
	// Scopes overrides discovery.scopes_supported. If empty, the server's scopes win.
	Scopes []string
	// RefreshToken is the current refresh token, if any (seeds the flow without a browser).
	RefreshToken string
	// RefreshTokenSecretRef is where to persist a (rotated) refresh token.
	RefreshTokenSecretRef SecretKeyRef
	// AccessTokenSecretRef is the Secret/key the model-router reads as the bearer
	// (referenced by MCPServer.spec.auth.bearerToken).
	AccessTokenSecretRef SecretKeyRef
	// RefreshInterval bounds how often to attempt a refresh when no expires_in is known.
	// A zero value defaults to 50m.
	RefreshInterval time.Duration
}

Config drives the token manager for one MCP server.

type CtrlSecretIO

type CtrlSecretIO struct {
	Client client.Client
}

CtrlSecretIO implements SecretIO against a controller-runtime client (the one the operator's controllers already hold). The MCPServer reconciler uses it to satisfy OAuth-managed MCPServers without a separate sidecar proxy.

func NewCtrlSecretIO

func NewCtrlSecretIO(c client.Client) *CtrlSecretIO

NewCtrlSecretIO returns a SecretIO backed by a controller-runtime client.

func (*CtrlSecretIO) ReadKey

func (c *CtrlSecretIO) ReadKey(ctx context.Context, namespace, name, key string) ([]byte, bool, error)

func (*CtrlSecretIO) WriteKey

func (c *CtrlSecretIO) WriteKey(ctx context.Context, namespace, name, key string, value []byte) error

type K8sSecrets

type K8sSecrets struct {
	Client kubernetes.Interface
}

K8sSecrets implements SecretIO over a Kubernetes cluster using client-go. It is what cmd/oauth-proxy uses at runtime; tests use the in-package fakeSecretIO.

func NewK8sSecrets

func NewK8sSecrets(client kubernetes.Interface) *K8sSecrets

NewK8sSecrets returns a SecretIO backed by a kubernetes clientset.

func (*K8sSecrets) ReadKey

func (k *K8sSecrets) ReadKey(ctx context.Context, namespace, name, key string) ([]byte, bool, error)

ReadKey returns the secret bytes for key, or (nil,false,nil) if absent.

func (*K8sSecrets) WriteKey

func (k *K8sSecrets) WriteKey(ctx context.Context, namespace, name, key string, value []byte) error

WriteKey creates-or-updates a single key in the Secret. Uses optimistic retries on conflicts so two writers (e.g. concurrent refresh) don't clobber.

type Metadata

type Metadata struct {
	AuthorizationEndpoint    string
	TokenEndpoint            string
	GrantTypes               []string
	CodeChallengeMethods     []string
	TokenEndpointAuthMethods []string
	ScopesSupported          []string
	BearerMethods            []string
	AccessTokenSecretRef     SecretKeyRef // where to write access_token
}

Metadata is the resolved, server-agnostic OAuth configuration for an MCP server.

type ProtectedResourceMetadata

type ProtectedResourceMetadata struct {
	AuthorizationServers     []string `json:"authorization_servers"`
	BearerMethodsSupported   []string `json:"bearer_methods_supported"`
	ScopesSupported          []string `json:"scopes_supported"`
	Resource                 string   `json:"resource"`
	TokenEndpointAuthMethods []string `json:"token_endpoint_auth_methods_supported,omitempty"`
}

ProtectedResourceMetadata is the response of /.well-known/oauth-protected-resource (RFC 9700 / MCP OAuth discovery).

type SecretIO

type SecretIO interface {
	// ReadKey returns the value and whether the key exists.
	ReadKey(ctx context.Context, namespace, name, key string) ([]byte, bool, error)
	// WriteKey creates-or-updates a single key in the Secret.
	WriteKey(ctx context.Context, namespace, name, key string, value []byte) error
}

SecretIO abstracts reading and writing Secret key/values so the manager is testable without a Kubernetes client. The Kubernetes implementation is k8sSecretIO.

type SecretKeyRef

type SecretKeyRef struct {
	Namespace string
	Name      string
	Key       string
}

SecretKeyRef references a Kubernetes Secret key.

type TokenManager

type TokenManager struct {
	HTTPClient *http.Client
	Secrets    SecretIO
}

TokenManager refreshes OAuth access tokens for an MCP server and persists them into a Secret. It is reusable across any OAuth-only MCP: discovery is driven by the server's published metadata, not by hardcoded endpoints.

func New

func New(httpClient *http.Client, secrets SecretIO) *TokenManager

New returns a TokenManager backed by the given HTTP client and Secret store.

func (*TokenManager) Discover

func (m *TokenManager) Discover(ctx context.Context, serverURL string) (*Metadata, error)

Discover resolves OAuth Metadata for an MCP server by following RFC 8414 / the MCP oauth-protected-resource discovery chain from ServerURL's issuer origin.

func (*TokenManager) ExchangeCode

func (m *TokenManager) ExchangeCode(ctx context.Context, meta *Metadata, cfg *Config, code, codeVerifier string) (*TokenResponse, error)

ExchangeCode exchanges an authorization code (PKCE) for access+refresh tokens. It is used by the one-time seed flow.

func (*TokenManager) LoginURL

func (m *TokenManager) LoginURL(meta *Metadata, cfg *Config) (authURL, codeVerifier string, err error)

LoginURL returns the authorization endpoint URL for the one-time browser code exchange (PKCE, S256). It also returns the generated code_verifier that the caller must preserve and pass to ExchangeCode.

func (*TokenManager) Refresh

func (m *TokenManager) Refresh(ctx context.Context, meta *Metadata, cfg *Config) (*TokenResponse, error)

Refresh exchanges a refresh token for a fresh access token at meta.TokenEndpoint. It uses client_secret_post (client_id/secret in the form body), the grant type the server advertises, and returns the parsed token response.

func (*TokenManager) RefreshOnce

func (m *TokenManager) RefreshOnce(ctx context.Context, meta *Metadata, cfg *Config) (*TokenResponse, error)

RefreshOnce re-reads the current refresh token from its secret (so rotated tokens persist across cycles/restarts), exchanges it for a fresh access token, and writes both the access token (to AccessTokenSecretRef) and any rotated refresh token (to RefreshTokenSecretRef). It returns the token response so callers can schedule the next refresh from resp.ExpiresIn.

func (*TokenManager) Run

func (m *TokenManager) Run(ctx context.Context, meta *Metadata, cfg *Config) error

Run periodically refreshes the access token for cfg until ctx is cancelled. It is the loop used by the (optional) oauth-proxy `run` subcommand. The operator reconciler calls RefreshOnce directly on its own requeue schedule instead.

type TokenResponse

type TokenResponse struct {
	OK           bool   `json:"ok"` // Slack: absent means standard OAuth (treat as success unless error)
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token,omitempty"` // absent => token not rotated; keep existing
	ExpiresIn    int    `json:"expires_in,omitempty"`    // seconds; absent => unknown lifetime
	TokenType    string `json:"token_type,omitempty"`
	Error        string `json:"error,omitempty"` // OAuth error code (e.g. invalid_grant) or Slack error
}

TokenResponse is the union of standard OAuth2 and Slack-style token responses.

func (*TokenResponse) ErrorOrMessage

func (t *TokenResponse) ErrorOrMessage() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL