Documentation
¶
Overview ¶
Package oauth implements OAuth 2.1 proxy functionality for remote MCP server authentication.
This package provides the server-side OAuth client and proxy implementation that allows the Muster Server to authenticate with remote MCP servers on behalf of users without exposing sensitive tokens to the Muster Agent.
Architecture ¶
The OAuth proxy follows a three-legged OAuth 2.1 Authorization Code flow:
- User requests a tool that requires authentication
- Muster Server detects 401 Unauthorized from the remote MCP server
- Muster Server generates an authorization URL and returns "Auth Required" challenge
- User authenticates via their browser with the Identity Provider
- Browser redirects to Muster Server's callback endpoint with authorization code
- Muster Server exchanges code for tokens and stores them securely
- User retries the original request, which now succeeds with the stored token
Components ¶
- TokenStore: In-memory storage for OAuth tokens, indexed by session and issuer
- StateStore: Manages OAuth state parameters for CSRF protection
- Client: Handles OAuth flows, code exchange, and token refresh
- Handler: HTTP handler for the /oauth/callback endpoint
- Manager: Coordinates OAuth flows and integrates with the aggregator
Security ¶
## Token Storage
By default, tokens are stored in-memory and are lost when the Muster Server restarts. When Valkey storage is configured (storage.type: valkey), tokens are persisted in Valkey, surviving pod restarts and enabling multi-pod deployments without sticky sessions.
The TokenStorer and StateStorer interfaces allow swapping between in-memory (default) and Valkey-backed implementations via functional options on the Manager constructor.
## Encryption at Rest (Valkey)
When using Valkey storage, token values (access tokens, refresh tokens, ID tokens, and OAuth state including code verifiers) are encrypted at rest using AES-256-GCM if an encryption key is configured (oauthServer.encryptionKey or encryptionKeyFile). The same key protects both the mcp-oauth server stores and the OAuth proxy stores.
Production deployments using Valkey MUST:
- Configure an AES-256 encryption key (32 bytes, base64-encoded)
- Enable TLS for Valkey connections (storage.valkey.tlsEnabled: true)
- Use Valkey ACLs to restrict access to the muster key prefix
- Ensure the Valkey storage volume is encrypted (e.g., via StorageClass)
Without encryption, a Valkey compromise (unauthorized access, RDB dump exfiltration, or replication snooping) exposes all stored tokens in plaintext.
## Session Isolation
Tokens are stored with a composite key of (SessionID, Issuer, Scope), where SessionID is the mcp-oauth token family ID. Each login creates a new token family, so tokens are isolated per login session. Logout on device A does not affect device B's tokens. Each token entry also records the owning user ID (sub claim) for bulk operations like "sign out everywhere".
For stdio transport (single-user CLI), a default session ID is used. This is acceptable since stdio is inherently single-user (one process = one user).
## TLS/HTTPS Requirements (CRITICAL)
Production deployments MUST use HTTPS for all OAuth-related endpoints:
Muster Server's public URL (oauth.publicUrl configuration): The OAuth callback endpoint receives authorization codes. Without HTTPS, attackers could intercept these codes and exchange them for tokens.
OAuth Issuer URLs: All communication with Identity Providers (metadata discovery, token exchange, token refresh) must be over HTTPS. The issuer's TLS certificate provides integrity and authenticity guarantees for OAuth metadata.
Remote MCP Server URLs: When MCP servers require OAuth authentication, their endpoints should use HTTPS to protect the bearer tokens in Authorization headers.
Without TLS, the following attacks become possible:
- Authorization code interception during OAuth callback
- Token theft via man-in-the-middle attacks
- Metadata manipulation to redirect token exchanges to malicious endpoints
- Bearer token theft from Authorization headers
## Rate Limiting Recommendations
The OAuth callback endpoint (/oauth/callback by default) should be protected by rate limiting at the infrastructure level (ingress controller, load balancer, or API gateway). Recommended limits:
- Per-IP rate limit: 10-20 requests per minute
- Global rate limit: 100-500 requests per minute (depending on expected user base)
Rate limiting protects against:
- Denial of service attacks on the OAuth callback endpoint
- Brute-force attempts to guess authorization codes or state parameters
- Resource exhaustion from excessive token exchange requests
Example Kubernetes Ingress annotation (nginx):
nginx.ingress.kubernetes.io/limit-rps: "10" nginx.ingress.kubernetes.io/limit-connections: "5"
## Logging Security
Session IDs are truncated in log output to prevent full session identifiers from appearing in logs. Only the first 8 characters are logged (e.g., "abc12345..."). Access tokens and refresh tokens are never logged.
Token refresh operations are logged at INFO level for operational monitoring, including duration metrics for performance tracking.
SSO Support ¶
The package supports Single Sign-On (SSO) through Token Forwarding and Token Exchange. When a user authenticates with muster, the token can be forwarded to downstream servers (Token Forwarding) or exchanged for a token valid on a remote IdP (Token Exchange).
Index ¶
- Constants
- func GetExpectedIssuer(config *api.TokenExchangeConfig) string
- type Adapter
- func (a *Adapter) ClearTokenByIssuer(sessionID, issuer string)
- func (a *Adapter) CreateAuthChallenge(ctx context.Context, sessionID, userID, serverName, issuer, scope string) (*api.AuthChallenge, error)
- func (a *Adapter) DeleteTokensBySession(sessionID string)
- func (a *Adapter) DeleteTokensByUser(userID string)
- func (a *Adapter) ExchangeTokenForRemoteCluster(ctx context.Context, localToken, userID string, ...) (string, error)
- func (a *Adapter) ExchangeTokenForRemoteClusterWithClient(ctx context.Context, localToken, userID string, ...) (string, error)
- func (a *Adapter) FindTokenWithIDToken(sessionID string) *api.OAuthToken
- func (a *Adapter) GetCIMDHandler() http.HandlerFunc
- func (a *Adapter) GetCIMDPath() string
- func (a *Adapter) GetCallbackPath() string
- func (a *Adapter) GetFullTokenByIssuer(sessionID, issuer string) *api.OAuthToken
- func (a *Adapter) GetHTTPHandler() http.Handler
- func (a *Adapter) GetToken(sessionID, serverName string) *api.OAuthToken
- func (a *Adapter) GetTokenByIssuer(sessionID, issuer string) *api.OAuthToken
- func (a *Adapter) IsEnabled() bool
- func (a *Adapter) Register()
- func (a *Adapter) RegisterServer(serverName, issuer, scope string)
- func (a *Adapter) SetAuthCompletionCallback(callback api.AuthCompletionCallback)
- func (a *Adapter) ShouldServeCIMD() bool
- func (a *Adapter) Stop()
- func (a *Adapter) StoreToken(sessionID, userID, issuer string, token *api.OAuthToken)
- type AuthCompletionCallback
- type AuthRequiredResponse
- type AuthServerConfig
- type Client
- func (c *Client) DiscoverMetadata(ctx context.Context, issuer string) (*pkgoauth.Metadata, error)
- func (c *Client) ExchangeCode(ctx context.Context, code, codeVerifier, issuer string) (*pkgoauth.Token, error)
- func (c *Client) GenerateAuthURL(ctx context.Context, sessionID, userID, serverName, issuer, scope string) (string, error)
- func (c *Client) GetCIMDURL() string
- func (c *Client) GetClientMetadata() *pkgoauth.ClientMetadata
- func (c *Client) GetRedirectURI() string
- func (c *Client) GetStateStore() StateStorer
- func (c *Client) GetToken(sessionID, issuer, scope string) *pkgoauth.Token
- func (c *Client) GetTokenStore() TokenStorer
- func (c *Client) SetHTTPClient(httpClient *http.Client)
- func (c *Client) Stop()
- func (c *Client) StoreToken(sessionID, userID string, token *pkgoauth.Token)
- type ClientOption
- type ExchangeRequest
- type ExchangeResult
- type Handler
- type Manager
- func (m *Manager) ClearTokenByIssuer(sessionID, issuer string)
- func (m *Manager) CreateAuthChallenge(ctx context.Context, sessionID, userID, serverName, issuer, scope string) (*AuthRequiredResponse, error)
- func (m *Manager) DeleteTokensBySession(sessionID string)
- func (m *Manager) DeleteTokensByUser(userID string)
- func (m *Manager) ExchangeTokenForRemoteCluster(ctx context.Context, localToken, userID string, ...) (string, error)
- func (m *Manager) ExchangeTokenForRemoteClusterWithClient(ctx context.Context, localToken, userID string, ...) (string, error)
- func (m *Manager) GetCIMDHandler() http.HandlerFunc
- func (m *Manager) GetCIMDPath() string
- func (m *Manager) GetCallbackPath() string
- func (m *Manager) GetHTTPHandler() http.Handler
- func (m *Manager) GetServerConfig(serverName string) *AuthServerConfig
- func (m *Manager) GetToken(sessionID, serverName string) *pkgoauth.Token
- func (m *Manager) GetTokenByIssuer(sessionID, issuer string) *pkgoauth.Token
- func (m *Manager) GetTokenExchanger() *TokenExchanger
- func (m *Manager) HandleCallback(ctx context.Context, code, state string) error
- func (m *Manager) IsEnabled() bool
- func (m *Manager) RegisterServer(serverName, issuer, scope string)
- func (m *Manager) SetAuthCompletionCallback(callback AuthCompletionCallback)
- func (m *Manager) ShouldServeCIMD() bool
- func (m *Manager) Stop()
- func (m *Manager) StoreToken(sessionID, userID, issuer string, token *pkgoauth.Token)
- type ManagerOption
- type OAuthState
- type StateStore
- type StateStorer
- type TokenExchanger
- func (e *TokenExchanger) Cleanup() int
- func (e *TokenExchanger) ClearAllCache()
- func (e *TokenExchanger) ClearCache(tokenEndpoint, connectorID, userID string)
- func (e *TokenExchanger) Exchange(ctx context.Context, req *ExchangeRequest) (*ExchangeResult, error)
- func (e *TokenExchanger) ExchangeWithClient(ctx context.Context, req *ExchangeRequest, httpClient *http.Client) (*ExchangeResult, error)
- func (e *TokenExchanger) GetCacheStats() oidc.TokenExchangeCacheStats
- type TokenExchangerOptions
- type TokenKey
- type TokenStore
- func (ts *TokenStore) Count() int
- func (ts *TokenStore) Delete(key TokenKey)
- func (ts *TokenStore) DeleteByIssuer(sessionID, issuer string)
- func (ts *TokenStore) DeleteBySession(sessionID string)
- func (ts *TokenStore) DeleteByUser(userID string)
- func (ts *TokenStore) Get(key TokenKey) *pkgoauth.Token
- func (ts *TokenStore) GetAllForSession(sessionID string) map[TokenKey]*pkgoauth.Token
- func (ts *TokenStore) GetAllForUser(userID string) map[TokenKey]*pkgoauth.Token
- func (ts *TokenStore) GetByIssuer(sessionID, issuer string) *pkgoauth.Token
- func (ts *TokenStore) Stop()
- func (ts *TokenStore) Store(key TokenKey, token *pkgoauth.Token, userID string)
- type TokenStorer
- type ValkeyStateStore
- type ValkeyTokenStore
- func (s *ValkeyTokenStore) Count() int
- func (s *ValkeyTokenStore) Delete(key TokenKey)
- func (s *ValkeyTokenStore) DeleteByIssuer(sessionID, issuer string)
- func (s *ValkeyTokenStore) DeleteBySession(sessionID string)
- func (s *ValkeyTokenStore) DeleteByUser(userID string)
- func (s *ValkeyTokenStore) Get(key TokenKey) *pkgoauth.Token
- func (s *ValkeyTokenStore) GetAllForSession(sessionID string) map[TokenKey]*pkgoauth.Token
- func (s *ValkeyTokenStore) GetByIssuer(sessionID, issuer string) *pkgoauth.Token
- func (s *ValkeyTokenStore) Stop()
- func (s *ValkeyTokenStore) Store(key TokenKey, token *pkgoauth.Token, userID string)
Constants ¶
const DefaultOIDCScopes = "openid profile email groups"
DefaultOIDCScopes is the default set of scopes requested for OIDC token exchange. These scopes provide identity (openid), user profile info (profile, email), and group membership (groups) for RBAC decisions.
const DefaultTokenStoreTTL = 30 * 24 * time.Hour
DefaultTokenStoreTTL is the session-level TTL for OAuth token entries in Valkey. Matching the capability store TTL (30 days) so tokens survive inactivity. Tokens self-expire based on their ExpiresAt; this TTL is only for Valkey key garbage collection of abandoned sessions.
Variables ¶
This section is empty.
Functions ¶
func GetExpectedIssuer ¶
func GetExpectedIssuer(config *api.TokenExchangeConfig) string
GetExpectedIssuer returns the expected issuer URL for token validation. If ExpectedIssuer is explicitly set in the config, it is used directly. Otherwise, the issuer is derived from DexTokenEndpoint (backward compatible).
This separation is important for proxied access scenarios:
- DexTokenEndpoint may go through a proxy (e.g., https://dex-cluster.proxy.example.com/token)
- ExpectedIssuer is the actual Dex issuer (e.g., https://dex.cluster-b.example.com)
Types ¶
type Adapter ¶
type Adapter struct {
// contains filtered or unexported fields
}
Adapter implements api.OAuthHandler by wrapping the OAuth Manager. This follows the service locator pattern where packages communicate through interfaces defined in the api package.
func NewAdapter ¶
NewAdapter creates a new OAuth API adapter wrapping the given manager.
func (*Adapter) ClearTokenByIssuer ¶
ClearTokenByIssuer removes all tokens for a given session and issuer.
func (*Adapter) CreateAuthChallenge ¶
func (a *Adapter) CreateAuthChallenge(ctx context.Context, sessionID, userID, serverName, issuer, scope string) (*api.AuthChallenge, error)
CreateAuthChallenge creates an authentication challenge for a 401 response.
func (*Adapter) DeleteTokensBySession ¶ added in v0.1.53
DeleteTokensBySession removes all downstream tokens for a given session.
func (*Adapter) DeleteTokensByUser ¶ added in v0.1.44
DeleteTokensByUser removes all downstream tokens for a given user across all sessions.
func (*Adapter) ExchangeTokenForRemoteCluster ¶
func (a *Adapter) ExchangeTokenForRemoteCluster(ctx context.Context, localToken, userID string, config *api.TokenExchangeConfig) (string, error)
ExchangeTokenForRemoteCluster exchanges a local token for one valid on a remote cluster. This implements RFC 8693 Token Exchange for cross-cluster SSO scenarios.
func (*Adapter) ExchangeTokenForRemoteClusterWithClient ¶
func (a *Adapter) ExchangeTokenForRemoteClusterWithClient(ctx context.Context, localToken, userID string, config *api.TokenExchangeConfig, httpClient *http.Client) (string, error)
ExchangeTokenForRemoteClusterWithClient exchanges a local token for one valid on a remote cluster using a custom HTTP client. This is used when the token exchange endpoint is accessed via Teleport Application Access, which requires mutual TLS authentication.
func (*Adapter) FindTokenWithIDToken ¶
func (a *Adapter) FindTokenWithIDToken(sessionID string) *api.OAuthToken
FindTokenWithIDToken searches for any token in the session that has an ID token. This is used as a fallback when the muster issuer is not explicitly configured.
func (*Adapter) GetCIMDHandler ¶
func (a *Adapter) GetCIMDHandler() http.HandlerFunc
GetCIMDHandler returns the HTTP handler for serving the CIMD.
func (*Adapter) GetCIMDPath ¶
GetCIMDPath returns the path for serving the CIMD.
func (*Adapter) GetCallbackPath ¶
GetCallbackPath returns the configured callback path.
func (*Adapter) GetFullTokenByIssuer ¶
func (a *Adapter) GetFullTokenByIssuer(sessionID, issuer string) *api.OAuthToken
GetFullTokenByIssuer retrieves the full token (including ID token if available) for the given session and issuer. Returns nil if no valid token exists.
func (*Adapter) GetHTTPHandler ¶
GetHTTPHandler returns the HTTP handler for OAuth callback endpoints.
func (*Adapter) GetToken ¶
func (a *Adapter) GetToken(sessionID, serverName string) *api.OAuthToken
GetToken retrieves a valid token for the given session and server.
func (*Adapter) GetTokenByIssuer ¶
func (a *Adapter) GetTokenByIssuer(sessionID, issuer string) *api.OAuthToken
GetTokenByIssuer retrieves a valid token for the given session and issuer.
func (*Adapter) Register ¶
func (a *Adapter) Register()
Register registers this adapter with the API layer.
func (*Adapter) RegisterServer ¶
RegisterServer registers OAuth configuration for a remote MCP server.
func (*Adapter) SetAuthCompletionCallback ¶
func (a *Adapter) SetAuthCompletionCallback(callback api.AuthCompletionCallback)
SetAuthCompletionCallback sets the callback to be called after successful authentication.
func (*Adapter) ShouldServeCIMD ¶
ShouldServeCIMD returns true if muster should serve its own CIMD.
func (*Adapter) Stop ¶
func (a *Adapter) Stop()
Stop stops the OAuth handler and cleans up resources.
func (*Adapter) StoreToken ¶ added in v0.0.231
func (a *Adapter) StoreToken(sessionID, userID, issuer string, token *api.OAuthToken)
StoreToken persists a token for the given session and issuer.
type AuthCompletionCallback ¶
type AuthCompletionCallback func(ctx context.Context, sessionID, userID, serverName, accessToken string) error
AuthCompletionCallback is called after successful OAuth authentication.
type AuthRequiredResponse ¶
type AuthRequiredResponse struct {
// Status indicates this is an auth required response.
Status string `json:"status"` // "auth_required"
// AuthURL is the OAuth authorization URL the user should visit.
AuthURL string `json:"auth_url"`
// ServerName is the name of the MCP server requiring authentication.
ServerName string `json:"server_name,omitempty"`
// Message is a human-readable description of why auth is needed.
Message string `json:"message,omitempty"`
}
AuthRequiredResponse represents an authentication challenge returned when a remote MCP server requires OAuth authentication. Note: This is different from pkgoauth.AuthChallenge which represents parsed WWW-Authenticate header data. This is a user-facing response.
type AuthServerConfig ¶
AuthServerConfig holds OAuth configuration for a specific remote MCP server.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client handles OAuth 2.1 flows for remote MCP server authentication.
func NewClient ¶
func NewClient(clientID, publicURL, callbackPath, cimdScopes string, opts ...ClientOption) *Client
NewClient creates a new OAuth client with the given configuration. By default, in-memory stores are used. Use WithTokenStorer / WithStateStorer to inject Valkey-backed implementations.
func (*Client) DiscoverMetadata ¶
DiscoverMetadata fetches OAuth metadata for an issuer. This is exposed for external access to metadata discovery.
func (*Client) ExchangeCode ¶
func (c *Client) ExchangeCode(ctx context.Context, code, codeVerifier, issuer string) (*pkgoauth.Token, error)
ExchangeCode exchanges an authorization code for tokens.
func (*Client) GenerateAuthURL ¶
func (c *Client) GenerateAuthURL(ctx context.Context, sessionID, userID, serverName, issuer, scope string) (string, error)
GenerateAuthURL creates an OAuth authorization URL for user authentication. Returns the URL. The code verifier is stored with the state for later retrieval.
func (*Client) GetCIMDURL ¶
GetCIMDURL returns the URL where the Client ID Metadata Document is served. This is derived from the clientID which is expected to be the CIMD URL.
func (*Client) GetClientMetadata ¶
func (c *Client) GetClientMetadata() *pkgoauth.ClientMetadata
GetClientMetadata returns the Client ID Metadata Document for this client.
func (*Client) GetRedirectURI ¶
GetRedirectURI returns the full redirect URI for OAuth callbacks.
func (*Client) GetStateStore ¶
func (c *Client) GetStateStore() StateStorer
GetStateStore returns the state store for external access.
func (*Client) GetToken ¶
GetToken retrieves a valid token for the given session and issuer. Returns nil if no valid token exists.
func (*Client) GetTokenStore ¶
func (c *Client) GetTokenStore() TokenStorer
GetTokenStore returns the token store for external access.
func (*Client) SetHTTPClient ¶
SetHTTPClient sets a custom HTTP client for the OAuth client. This is useful for testing.
type ClientOption ¶ added in v0.1.69
type ClientOption func(*Client)
ClientOption configures optional Client parameters.
func WithStateStorer ¶ added in v0.1.69
func WithStateStorer(ss StateStorer) ClientOption
WithStateStorer sets a custom StateStorer implementation (e.g., Valkey-backed).
func WithTokenStorer ¶ added in v0.1.69
func WithTokenStorer(ts TokenStorer) ClientOption
WithTokenStorer sets a custom TokenStorer implementation (e.g., Valkey-backed).
type ExchangeRequest ¶
type ExchangeRequest struct {
// Config is the token exchange configuration for the target cluster.
// Uses the API type directly to avoid duplication (DRY principle).
Config *api.TokenExchangeConfig
// SubjectToken is the local token to exchange (ID token or access token).
SubjectToken string
// SubjectTokenType specifies whether SubjectToken is an ID token or access token.
// Use oidc.TokenTypeIDToken or oidc.TokenTypeAccessToken.
// Defaults to TokenTypeIDToken if not specified.
SubjectTokenType string
// UserID is extracted from the validated subject token's "sub" claim.
// CRITICAL: This must come from validated JWT claims, not user input.
// Used for cache key generation.
UserID string
}
ExchangeRequest contains the parameters for a token exchange operation.
type ExchangeResult ¶
type ExchangeResult struct {
// AccessToken is the exchanged token valid on the remote cluster.
AccessToken string
// IssuedTokenType is the type of the issued token.
IssuedTokenType string
// FromCache indicates whether the token was served from cache.
FromCache bool
}
ExchangeResult contains the result of a successful token exchange.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler provides HTTP handlers for OAuth callback endpoints.
func NewHandler ¶
NewHandler creates a new OAuth HTTP handler.
func (*Handler) HandleCallback ¶
func (h *Handler) HandleCallback(w http.ResponseWriter, r *http.Request)
HandleCallback handles the OAuth callback endpoint. This is called by the browser after the user authenticates with the IdP.
func (*Handler) ServeCIMD ¶
func (h *Handler) ServeCIMD(w http.ResponseWriter, r *http.Request)
ServeCIMD handles GET requests to serve the Client ID Metadata Document (CIMD). This allows muster to self-host its own CIMD without requiring external static hosting. The CIMD is dynamically generated from the OAuth configuration.
func (*Handler) ServeHTTP ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler for the OAuth handler.
func (*Handler) SetManager ¶
SetManager sets the manager reference for callback handling. This is called by the Manager after creating the Handler.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager coordinates OAuth flows for remote MCP server authentication. It manages the OAuth MCP client, HTTP handlers, and integrates with the aggregator.
func NewManager ¶
func NewManager(cfg config.OAuthMCPClientConfig, opts ...ManagerOption) *Manager
NewManager creates a new OAuth manager with the given configuration. The cfg parameter contains the OAuth MCP client/proxy configuration for authenticating TO remote MCP servers.
func (*Manager) ClearTokenByIssuer ¶
ClearTokenByIssuer removes all tokens for a given session and issuer.
func (*Manager) CreateAuthChallenge ¶
func (m *Manager) CreateAuthChallenge(ctx context.Context, sessionID, userID, serverName, issuer, scope string) (*AuthRequiredResponse, error)
CreateAuthChallenge creates an authentication challenge for a 401 response. Returns the auth URL the user should visit and the challenge response.
func (*Manager) DeleteTokensBySession ¶ added in v0.1.53
DeleteTokensBySession removes all downstream tokens for a given session. This is used during per-session logout via token family revocation.
func (*Manager) DeleteTokensByUser ¶ added in v0.1.44
DeleteTokensByUser removes all downstream tokens for a given user across all sessions. This is used during "sign out everywhere" to clear all server-side token state.
func (*Manager) ExchangeTokenForRemoteCluster ¶
func (m *Manager) ExchangeTokenForRemoteCluster(ctx context.Context, localToken, userID string, config *api.TokenExchangeConfig) (string, error)
ExchangeTokenForRemoteCluster exchanges a local token for one valid on a remote cluster. This implements RFC 8693 Token Exchange for cross-cluster SSO scenarios.
Args:
- ctx: Context for the operation
- localToken: The local ID token to exchange
- userID: The user's unique identifier (from validated JWT 'sub' claim)
- config: Token exchange configuration for the remote cluster
Returns the exchanged access token, or an error if exchange fails.
func (*Manager) ExchangeTokenForRemoteClusterWithClient ¶
func (m *Manager) ExchangeTokenForRemoteClusterWithClient(ctx context.Context, localToken, userID string, config *api.TokenExchangeConfig, httpClient *http.Client) (string, error)
ExchangeTokenForRemoteClusterWithClient exchanges a local token for one valid on a remote cluster using a custom HTTP client. This is used when the token exchange endpoint is accessed via Teleport Application Access, which requires mutual TLS authentication.
The httpClient parameter should be configured with the appropriate TLS certificates (e.g., Teleport Machine ID certificates). If nil, uses the default HTTP client.
Args:
- ctx: Context for the operation
- localToken: The local ID token to exchange
- userID: The user's unique identifier (from validated JWT 'sub' claim)
- config: Token exchange configuration for the remote cluster
- httpClient: Custom HTTP client with Teleport TLS certificates (or nil for default)
Returns the exchanged access token, or an error if exchange fails.
func (*Manager) GetCIMDHandler ¶
func (m *Manager) GetCIMDHandler() http.HandlerFunc
GetCIMDHandler returns the HTTP handler for serving the CIMD.
func (*Manager) GetCIMDPath ¶
GetCIMDPath returns the path for serving the CIMD.
func (*Manager) GetCallbackPath ¶
GetCallbackPath returns the configured callback path.
func (*Manager) GetHTTPHandler ¶
GetHTTPHandler returns the HTTP handler for OAuth endpoints.
func (*Manager) GetServerConfig ¶
func (m *Manager) GetServerConfig(serverName string) *AuthServerConfig
GetServerConfig returns the OAuth configuration for a server.
func (*Manager) GetToken ¶
GetToken retrieves a valid token for the given session and server. mcp-go handles token refresh via its transport layer, so this method simply returns the stored token without proactive refresh.
func (*Manager) GetTokenByIssuer ¶
GetTokenByIssuer retrieves a valid token for the given session and issuer. This is used for SSO when we have the issuer from a 401 response.
func (*Manager) GetTokenExchanger ¶
func (m *Manager) GetTokenExchanger() *TokenExchanger
GetTokenExchanger returns the token exchanger for direct access. This is useful for cache management and monitoring.
func (*Manager) HandleCallback ¶
HandleCallback processes an OAuth callback and stores the token. Note: This is a programmatic API for testing. The production flow uses Handler.HandleCallback which is the actual HTTP endpoint and handles the auth completion callback invocation.
func (*Manager) RegisterServer ¶
RegisterServer registers OAuth configuration for a remote MCP server.
func (*Manager) SetAuthCompletionCallback ¶
func (m *Manager) SetAuthCompletionCallback(callback AuthCompletionCallback)
SetAuthCompletionCallback sets the callback to be called after successful authentication. The aggregator uses this to establish session connections after browser OAuth completes.
func (*Manager) ShouldServeCIMD ¶
ShouldServeCIMD returns true if muster should serve its own CIMD.
type ManagerOption ¶ added in v0.1.69
type ManagerOption func(*managerOptions)
ManagerOption configures optional Manager parameters.
func WithValkeyStateStore ¶ added in v0.1.69
func WithValkeyStateStore(ss StateStorer) ManagerOption
WithValkeyStateStore injects a Valkey-backed StateStorer into the OAuth client.
func WithValkeyTokenStore ¶ added in v0.1.69
func WithValkeyTokenStore(ts TokenStorer) ManagerOption
WithValkeyTokenStore injects a Valkey-backed TokenStorer into the OAuth client.
type OAuthState ¶
type OAuthState struct {
// SessionID links the OAuth flow to the login session (token family ID).
SessionID string `json:"session_id"`
// UserID is the user's identity (sub claim), carried for token store indexing.
UserID string `json:"user_id"`
// ServerName is the MCP server that requires authentication.
ServerName string `json:"server_name"`
// Nonce is a random value for CSRF protection.
Nonce string `json:"nonce"`
// CreatedAt is when the state was created (for expiration).
CreatedAt time.Time `json:"created_at"`
// RedirectURI is where to redirect after callback processing.
RedirectURI string `json:"redirect_uri,omitempty"`
// Issuer is the OAuth issuer URL for token exchange.
Issuer string `json:"issuer,omitempty"`
// CodeVerifier is the PKCE code verifier for this flow.
// Stored server-side only, not transmitted in the state parameter.
CodeVerifier string `json:"-"`
}
OAuthState represents the state parameter data for OAuth flows. This is serialized and passed through the OAuth flow to link the callback to the original request. This is server-specific as it handles CSRF protection for server-side OAuth.
type StateStore ¶
type StateStore struct {
// contains filtered or unexported fields
}
StateStore provides thread-safe in-memory storage for OAuth state parameters. State parameters are used to link OAuth callbacks to original requests and provide CSRF protection.
IMPORTANT: StateStore starts a background goroutine for cleanup. Callers MUST call Stop() when done to prevent goroutine leaks. Typically this is done via defer after creating the store, or in a shutdown hook for long-lived stores.
func NewStateStore ¶
func NewStateStore() *StateStore
NewStateStore creates a new state store with default expiration.
func (*StateStore) Delete ¶
func (ss *StateStore) Delete(nonce string)
Delete removes a state from the store.
func (*StateStore) GenerateState ¶
func (ss *StateStore) GenerateState(sessionID, userID, serverName, issuer, codeVerifier string) (encodedState string, err error)
GenerateState creates a new OAuth state parameter and stores it. Returns the encoded state string to include in the authorization URL. The nonce is embedded within the encoded state and used for server-side lookup.
Args:
- subject: The user's identity
- serverName: The MCP server name requiring authentication
- issuer: The OAuth issuer URL
- codeVerifier: The PKCE code verifier for this flow
func (*StateStore) ValidateState ¶
func (ss *StateStore) ValidateState(encodedState string) *OAuthState
ValidateState validates an OAuth state parameter from a callback. Returns the original state data if valid, nil if invalid or expired.
type StateStorer ¶ added in v0.1.69
type StateStorer interface {
// GenerateState creates a new OAuth state, stores it, and returns the
// base64-encoded state string to include in the authorization URL.
GenerateState(sessionID, userID, serverName, issuer, codeVerifier string) (encodedState string, err error)
// ValidateState validates an encoded state from a callback. Returns the
// original state if valid; nil if invalid, expired, or already consumed.
// Valid states are consumed (single-use) to prevent replay attacks.
ValidateState(encodedState string) *OAuthState
// Delete removes a state entry by nonce.
Delete(nonce string)
// Stop releases resources (background goroutines, connections, etc.).
Stop()
}
StateStorer is the interface for OAuth state parameter storage. Implementations must be safe for concurrent use.
type TokenExchanger ¶
type TokenExchanger struct {
// contains filtered or unexported fields
}
TokenExchanger performs RFC 8693 OAuth 2.0 Token Exchange for cross-cluster SSO. It enables users authenticated to muster (Cluster A) to access MCP servers on remote clusters (Cluster B) by exchanging their local token for a token valid on the remote cluster's Identity Provider.
This is different from token forwarding (ForwardToken), which forwards muster's ID token directly. Token exchange is useful when:
- Remote clusters have separate Dex instances
- The remote Dex is configured with an OIDC connector pointing to muster's Dex
- You need a token issued by the remote cluster's IdP
Thread-safe: Yes, the underlying TokenExchangeClient is thread-safe.
func NewTokenExchangerWithOptions ¶
func NewTokenExchangerWithOptions(opts TokenExchangerOptions) *TokenExchanger
NewTokenExchangerWithOptions creates a new TokenExchanger with custom options.
func (*TokenExchanger) Cleanup ¶
func (e *TokenExchanger) Cleanup() int
Cleanup removes expired tokens from the cache. This should be called periodically for long-running services.
func (*TokenExchanger) ClearAllCache ¶
func (e *TokenExchanger) ClearAllCache()
ClearAllCache removes all cached tokens.
func (*TokenExchanger) ClearCache ¶
func (e *TokenExchanger) ClearCache(tokenEndpoint, connectorID, userID string)
ClearCache removes a cached token for the given parameters. This is useful when a cached token is rejected by the remote server.
func (*TokenExchanger) Exchange ¶
func (e *TokenExchanger) Exchange(ctx context.Context, req *ExchangeRequest) (*ExchangeResult, error)
Exchange exchanges a local token for a token valid on a remote cluster. The token is cached to reduce the number of exchange requests.
Args:
- ctx: Context for cancellation and timeouts
- req: Exchange request parameters
Returns the exchanged token or an error if exchange fails.
func (*TokenExchanger) ExchangeWithClient ¶
func (e *TokenExchanger) ExchangeWithClient(ctx context.Context, req *ExchangeRequest, httpClient *http.Client) (*ExchangeResult, error)
ExchangeWithClient exchanges a local token for a token valid on a remote cluster using a custom HTTP client. This is used when the token exchange endpoint is accessed via Teleport Application Access, which requires mutual TLS authentication.
The httpClient parameter should be configured with the appropriate TLS certificates (e.g., Teleport Machine ID certificates). If nil, uses the default exchanger client.
Args:
- ctx: Context for cancellation and timeouts
- req: Exchange request parameters
- httpClient: Custom HTTP client with Teleport TLS certificates (or nil for default)
Returns the exchanged token or an error if exchange fails.
func (*TokenExchanger) GetCacheStats ¶
func (e *TokenExchanger) GetCacheStats() oidc.TokenExchangeCacheStats
GetCacheStats returns statistics about the token exchange cache.
type TokenExchangerOptions ¶
type TokenExchangerOptions struct {
// Logger for debug/info messages (nil uses default logger).
Logger *slog.Logger
// AllowPrivateIP allows token endpoints to resolve to private IP addresses.
// WARNING: Reduces SSRF protection. Only enable for internal/VPN deployments.
AllowPrivateIP bool
// CacheMaxEntries is the maximum number of cached tokens (0 = default: 10000).
CacheMaxEntries int
// HTTPClient is the HTTP client to use for token exchange requests.
// If nil, an appropriate client is created based on AllowPrivateIP setting.
// Use this to configure custom TLS settings (e.g., for self-signed certs).
HTTPClient *http.Client
}
TokenExchangerOptions configures the TokenExchanger.
type TokenKey ¶
TokenKey uniquely identifies a token in the store. Tokens are indexed by session ID (token family), issuer, and scope to enable per-login-session isolation. Each login creates a new token family, and tokens are scoped to that family so logout on one device does not affect another.
type TokenStore ¶
type TokenStore struct {
// contains filtered or unexported fields
}
TokenStore provides thread-safe in-memory storage for OAuth tokens. Tokens are indexed by session ID (token family), issuer, and scope to support per-login-session isolation. Each entry also records the owning user ID to support bulk operations like "sign out everywhere".
IMPORTANT: TokenStore starts a background goroutine for cleanup. Callers MUST call Stop() when done to prevent goroutine leaks. Typically this is done via defer after creating the store, or in a shutdown hook for long-lived stores.
func NewTokenStore ¶
func NewTokenStore() *TokenStore
NewTokenStore creates a new in-memory token store. It starts a background goroutine for periodic cleanup of expired tokens.
func (*TokenStore) Count ¶
func (ts *TokenStore) Count() int
Count returns the number of tokens in the store.
func (*TokenStore) Delete ¶
func (ts *TokenStore) Delete(key TokenKey)
Delete removes a token from the store.
func (*TokenStore) DeleteByIssuer ¶
func (ts *TokenStore) DeleteByIssuer(sessionID, issuer string)
DeleteByIssuer removes all tokens for a given session and issuer. This is used to clear invalid/expired tokens before requesting fresh authentication.
func (*TokenStore) DeleteBySession ¶
func (ts *TokenStore) DeleteBySession(sessionID string)
DeleteBySession removes all tokens for a given session. This is used during per-session logout via token family revocation.
func (*TokenStore) DeleteByUser ¶ added in v0.1.40
func (ts *TokenStore) DeleteByUser(userID string)
DeleteByUser removes all tokens for a given user across all sessions. This is used during "sign out everywhere" to clear all server-side token state.
func (*TokenStore) Get ¶
func (ts *TokenStore) Get(key TokenKey) *pkgoauth.Token
Get retrieves a token from the store by key. Returns nil if the token doesn't exist or has expired.
func (*TokenStore) GetAllForSession ¶
func (ts *TokenStore) GetAllForSession(sessionID string) map[TokenKey]*pkgoauth.Token
GetAllForSession returns all valid tokens for a session.
func (*TokenStore) GetAllForUser ¶ added in v0.1.40
func (ts *TokenStore) GetAllForUser(userID string) map[TokenKey]*pkgoauth.Token
GetAllForUser returns all valid tokens for a user across all sessions. This iterates all entries and filters by the stored user ID.
func (*TokenStore) GetByIssuer ¶
func (ts *TokenStore) GetByIssuer(sessionID, issuer string) *pkgoauth.Token
GetByIssuer finds a token for the given session and issuer, regardless of scope. This enables SSO when the exact scope doesn't match but the issuer does.
When multiple tokens match (e.g., an ID-only token from SetSessionCreationHandler and a full token from a downstream OAuth callback), tokens with an AccessToken are preferred. This prevents non-deterministic map iteration from returning an ID-only token that would cause DynamicAuthClient to report ErrNoToken.
type TokenStorer ¶ added in v0.1.69
type TokenStorer interface {
// Store saves a token indexed by the given key, recording userID for
// reverse-lookup operations (e.g., "sign out everywhere").
Store(key TokenKey, token *pkgoauth.Token, userID string)
// Get retrieves a token by exact key. Returns nil if not found or expired.
Get(key TokenKey) *pkgoauth.Token
// GetByIssuer finds a token for the given session and issuer, regardless
// of scope. Returns nil if not found or expired.
GetByIssuer(sessionID, issuer string) *pkgoauth.Token
// GetAllForSession returns all valid (non-expired) tokens for a session.
GetAllForSession(sessionID string) map[TokenKey]*pkgoauth.Token
// Delete removes a single token by key.
Delete(key TokenKey)
// DeleteByUser removes all tokens for a user across all sessions.
DeleteByUser(userID string)
// DeleteBySession removes all tokens for a session.
DeleteBySession(sessionID string)
// DeleteByIssuer removes all tokens for a session+issuer combination.
DeleteByIssuer(sessionID, issuer string)
// Count returns the total number of tokens in the store.
Count() int
// Stop releases resources (background goroutines, connections, etc.).
Stop()
}
TokenStorer is the interface for OAuth token storage. Implementations must be safe for concurrent use. The aggregator-side OAuth proxy stores tokens here after successful authentication flows.
type ValkeyStateStore ¶ added in v0.1.69
type ValkeyStateStore struct {
// contains filtered or unexported fields
}
ValkeyStateStore stores OAuth state parameters in Valkey with automatic expiration and optional AES-256-GCM encryption at rest. Each state is stored as a single key with a 10-minute TTL, matching the in-memory StateStore behaviour.
Data model:
Key: {keyPrefix}oauth:state:{nonce}
Value: [encrypted] JSON(valkeyStateEntry)
TTL: 10 minutes
func NewValkeyStateStore ¶ added in v0.1.69
func NewValkeyStateStore(client valkey.Client, keyPrefix string, encryptor *security.Encryptor) *ValkeyStateStore
NewValkeyStateStore creates a Valkey-backed OAuth state store. keyPrefix is prepended to all Valkey keys (default "muster:" if empty). encryptor enables AES-256-GCM encryption at rest; pass nil to disable.
func (*ValkeyStateStore) Delete ¶ added in v0.1.69
func (s *ValkeyStateStore) Delete(nonce string)
func (*ValkeyStateStore) GenerateState ¶ added in v0.1.69
func (s *ValkeyStateStore) GenerateState(sessionID, userID, serverName, issuer, codeVerifier string) (string, error)
func (*ValkeyStateStore) Stop ¶ added in v0.1.69
func (s *ValkeyStateStore) Stop()
Stop is a no-op for the Valkey implementation (no background goroutines). The Valkey client is closed separately during server shutdown.
func (*ValkeyStateStore) ValidateState ¶ added in v0.1.69
func (s *ValkeyStateStore) ValidateState(encodedState string) *OAuthState
type ValkeyTokenStore ¶ added in v0.1.69
type ValkeyTokenStore struct {
// contains filtered or unexported fields
}
ValkeyTokenStore stores OAuth tokens in Valkey hashes with optional AES-256-GCM encryption at rest.
Data model:
Session key: {keyPrefix}oauth:token:{sessionID}
Fields: {issuer}|{scope} -> [encrypted] JSON(valkeyTokenEntry)
TTL: session-level, reset on every Store
User index: {keyPrefix}oauth:token:user:{userID}
Members: sessionIDs (for DeleteByUser reverse lookup)
TTL: same as session key
func NewValkeyTokenStore ¶ added in v0.1.69
func NewValkeyTokenStore(client valkey.Client, ttl time.Duration, keyPrefix string, encryptor *security.Encryptor) *ValkeyTokenStore
NewValkeyTokenStore creates a Valkey-backed OAuth token store. keyPrefix is prepended to all Valkey keys (default "muster:" if empty). encryptor enables AES-256-GCM encryption at rest; pass nil to disable.
func (*ValkeyTokenStore) Count ¶ added in v0.1.69
func (s *ValkeyTokenStore) Count() int
Count returns the total number of tokens across all sessions. This operation is bounded by a 10-second timeout to prevent Valkey overload. Intended for diagnostics only; avoid calling on hot paths.
func (*ValkeyTokenStore) Delete ¶ added in v0.1.69
func (s *ValkeyTokenStore) Delete(key TokenKey)
func (*ValkeyTokenStore) DeleteByIssuer ¶ added in v0.1.69
func (s *ValkeyTokenStore) DeleteByIssuer(sessionID, issuer string)
func (*ValkeyTokenStore) DeleteBySession ¶ added in v0.1.69
func (s *ValkeyTokenStore) DeleteBySession(sessionID string)
func (*ValkeyTokenStore) DeleteByUser ¶ added in v0.1.69
func (s *ValkeyTokenStore) DeleteByUser(userID string)
func (*ValkeyTokenStore) Get ¶ added in v0.1.69
func (s *ValkeyTokenStore) Get(key TokenKey) *pkgoauth.Token
func (*ValkeyTokenStore) GetAllForSession ¶ added in v0.1.69
func (s *ValkeyTokenStore) GetAllForSession(sessionID string) map[TokenKey]*pkgoauth.Token
func (*ValkeyTokenStore) GetByIssuer ¶ added in v0.1.69
func (s *ValkeyTokenStore) GetByIssuer(sessionID, issuer string) *pkgoauth.Token
func (*ValkeyTokenStore) Stop ¶ added in v0.1.69
func (s *ValkeyTokenStore) Stop()
Stop is a no-op for the Valkey implementation (no background goroutines). The Valkey client is closed separately during server shutdown.