oauth

package
v0.1.90 Latest Latest
Warning

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

Go to latest
Published: Mar 30, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

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:

  1. User requests a tool that requires authentication
  2. Muster Server detects 401 Unauthorized from the remote MCP server
  3. Muster Server generates an authorization URL and returns "Auth Required" challenge
  4. User authenticates via their browser with the Identity Provider
  5. Browser redirects to Muster Server's callback endpoint with authorization code
  6. Muster Server exchanges code for tokens and stores them securely
  7. 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

View Source
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.

View Source
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:

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

func NewAdapter(manager *Manager) *Adapter

NewAdapter creates a new OAuth API adapter wrapping the given manager.

func (*Adapter) ClearTokenByIssuer

func (a *Adapter) ClearTokenByIssuer(sessionID, issuer string)

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

func (a *Adapter) DeleteTokensBySession(sessionID string)

DeleteTokensBySession removes all downstream tokens for a given session.

func (*Adapter) DeleteTokensByUser added in v0.1.44

func (a *Adapter) DeleteTokensByUser(userID string)

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

func (a *Adapter) GetCIMDPath() string

GetCIMDPath returns the path for serving the CIMD.

func (*Adapter) GetCallbackPath

func (a *Adapter) GetCallbackPath() string

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

func (a *Adapter) GetHTTPHandler() http.Handler

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) IsEnabled

func (a *Adapter) IsEnabled() bool

IsEnabled returns whether OAuth proxy functionality is active.

func (*Adapter) Register

func (a *Adapter) Register()

Register registers this adapter with the API layer.

func (*Adapter) RegisterServer

func (a *Adapter) RegisterServer(serverName, issuer, scope string)

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

func (a *Adapter) ShouldServeCIMD() bool

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

type AuthServerConfig struct {
	ServerName string
	Issuer     string
	Scope      string
}

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

func (c *Client) DiscoverMetadata(ctx context.Context, issuer string) (*pkgoauth.Metadata, error)

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

func (c *Client) GetCIMDURL() string

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

func (c *Client) GetRedirectURI() string

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

func (c *Client) GetToken(sessionID, issuer, scope string) *pkgoauth.Token

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

func (c *Client) SetHTTPClient(httpClient *http.Client)

SetHTTPClient sets a custom HTTP client for the OAuth client. This is useful for testing.

func (*Client) Stop

func (c *Client) Stop()

Stop stops background cleanup goroutines.

func (*Client) StoreToken

func (c *Client) StoreToken(sessionID, userID string, token *pkgoauth.Token)

StoreToken stores a token in the token store.

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

func NewHandler(client *Client) *Handler

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

func (h *Handler) SetManager(manager *Manager)

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

func (m *Manager) ClearTokenByIssuer(sessionID, issuer string)

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

func (m *Manager) DeleteTokensBySession(sessionID string)

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

func (m *Manager) DeleteTokensByUser(userID string)

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

func (m *Manager) GetCIMDPath() string

GetCIMDPath returns the path for serving the CIMD.

func (*Manager) GetCallbackPath

func (m *Manager) GetCallbackPath() string

GetCallbackPath returns the configured callback path.

func (*Manager) GetHTTPHandler

func (m *Manager) GetHTTPHandler() http.Handler

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

func (m *Manager) GetToken(sessionID, serverName string) *pkgoauth.Token

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

func (m *Manager) GetTokenByIssuer(sessionID, issuer string) *pkgoauth.Token

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

func (m *Manager) HandleCallback(ctx context.Context, code, state string) error

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) IsEnabled

func (m *Manager) IsEnabled() bool

IsEnabled returns whether OAuth proxy is enabled.

func (*Manager) RegisterServer

func (m *Manager) RegisterServer(serverName, issuer, scope string)

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

func (m *Manager) ShouldServeCIMD() bool

ShouldServeCIMD returns true if muster should serve its own CIMD.

func (*Manager) Stop

func (m *Manager) Stop()

Stop stops the OAuth manager and cleans up resources.

func (*Manager) StoreToken added in v0.0.231

func (m *Manager) StoreToken(sessionID, userID, issuer string, token *pkgoauth.Token)

StoreToken persists a token for the given session and issuer. The userID is stored alongside for reverse-lookup by user.

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) Stop

func (ss *StateStore) Stop()

Stop stops the background cleanup goroutine.

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

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

type TokenKey struct {
	SessionID string
	Issuer    string
	Scope     string
}

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.

func (*TokenStore) Stop

func (ts *TokenStore) Stop()

Stop stops the background cleanup goroutine.

func (*TokenStore) Store

func (ts *TokenStore) Store(key TokenKey, token *pkgoauth.Token, userID string)

Store saves a token in the store, indexed by the given key. The userID is stored alongside for reverse-lookup by user (e.g., "sign out everywhere").

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.

func (*ValkeyTokenStore) Store added in v0.1.69

func (s *ValkeyTokenStore) Store(key TokenKey, token *pkgoauth.Token, userID string)

Jump to

Keyboard shortcuts

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