Documentation
¶
Overview ¶
Package oauth implements OAuth 2.1 client authentication for the Muster Agent.
This package provides secure token storage and SSO capabilities for the Agent when connecting to OAuth-protected Muster Servers and remote MCP servers.
Architecture ¶
The Agent OAuth client provides:
- XDG-compliant token storage with issuer-based lookup for SSO
- Token retrieval by issuer URL for cross-server SSO
- PKCE-enhanced Authorization Code Flow support
- Local HTTP callback listener for OAuth redirects
- Automatic token refresh and retry logic
Token Storage ¶
Tokens are stored in XDG-compliant location:
~/.config/muster/tokens/{server-hash}.json
Each token file contains both the server URL and issuer URL, enabling:
- Direct lookup by server URL (GetToken)
- Issuer-based lookup for SSO (GetByIssuer)
SSO Support ¶
The TokenStore supports Single Sign-On through issuer-based lookup. When a remote MCP server requires authentication with an issuer the Agent has already authenticated to, the existing token can be reused via GetByIssuer().
Usage ¶
// Create token store
store, err := oauth.NewTokenStore(oauth.TokenStoreConfig{
StorageDir: "~/.config/muster/tokens",
FileMode: true,
})
// Get token by server URL
token := store.GetToken(serverURL)
// Get token by issuer for SSO
token := store.GetByIssuer(issuerURL)
Index ¶
- Constants
- Variables
- func IsTokenExpiredError(err error) bool
- func OpenBrowser(urlStr string) error
- type AuthFlow
- type AuthFlowOptions
- type AuthManager
- func (m *AuthManager) CheckConnection(ctx context.Context, serverURL string) (AuthState, error)
- func (m *AuthManager) ClearToken() error
- func (m *AuthManager) Close() error
- func (m *AuthManager) GetAccessToken() (string, error)
- func (m *AuthManager) GetAuthChallenge() *pkgoauth.AuthChallenge
- func (m *AuthManager) GetAuthURL() string
- func (m *AuthManager) GetBearerToken() (string, error)
- func (m *AuthManager) GetLastError() error
- func (m *AuthManager) GetServerURL() string
- func (m *AuthManager) GetState() AuthState
- func (m *AuthManager) GetStoredToken() *StoredToken
- func (m *AuthManager) GetStoredTokenForEndpoint(endpoint string) *StoredToken
- func (m *AuthManager) HasValidTokenForEndpoint(endpoint string) bool
- func (m *AuthManager) StartAuthFlow(ctx context.Context) (string, error)
- func (m *AuthManager) StartAuthFlowSilent(ctx context.Context, loginHint, idTokenHint string) (string, error)
- func (m *AuthManager) WaitForAuth(ctx context.Context) error
- type AuthManagerConfig
- type AuthState
- type CallbackResult
- type CallbackServer
- type Client
- func (c *Client) ClearToken(serverURL string) error
- func (c *Client) Close() error
- func (c *Client) CompleteAuthFlow(ctx context.Context, serverURL, issuerURL string) (authURL string, waitFn func() (*oauth2.Token, error), err error)
- func (c *Client) CompleteAuthFlowWithOptions(ctx context.Context, serverURL, issuerURL string, opts *AuthFlowOptions) (authURL string, waitFn func() (*oauth2.Token, error), err error)
- func (c *Client) GetCurrentFlowServerURL() string
- func (c *Client) GetHTTPClient() *http.Client
- func (c *Client) GetToken(serverURL string) (*oauth2.Token, error)
- func (c *Client) HasValidToken(serverURL string) bool
- func (c *Client) IsFlowInProgress() bool
- func (c *Client) RefreshTokenIfNeeded(ctx context.Context, serverURL string) (bool, error)
- func (c *Client) StartAuthFlow(ctx context.Context, serverURL, issuerURL string) (string, error)
- func (c *Client) StartAuthFlowWithOptions(ctx context.Context, serverURL, issuerURL string, opts *AuthFlowOptions) (string, error)
- func (c *Client) WaitForCallback(ctx context.Context) (*oauth2.Token, error)
- type ClientConfig
- type OAuthMetadata
- type StoredToken
- type TokenStore
- func (s *TokenStore) Clear() error
- func (s *TokenStore) DeleteToken(serverURL string) error
- func (s *TokenStore) GetByIssuer(issuerURL string) *StoredToken
- func (s *TokenStore) GetToken(serverURL string) *StoredToken
- func (s *TokenStore) GetTokenIncludingExpiring(serverURL string) *StoredToken
- func (s *TokenStore) HasValidToken(serverURL string) bool
- func (s *TokenStore) HasValidTokenForIssuer(issuerURL string) bool
- func (s *TokenStore) StoreToken(serverURL, issuerURL string, token *oauth2.Token) error
- type TokenStoreConfig
Constants ¶
const CallbackTimeout = 10 * time.Minute
CallbackTimeout is how long to wait for the OAuth callback.
const DefaultAgentClientID = "https://giantswarm.github.io/muster/muster-agent.json"
DefaultAgentClientID is the CIMD URL for the Muster Agent. This is hosted on GitHub Pages and serves as the client_id for OAuth.
const DefaultCallbackPort = 3000
DefaultCallbackPort is the default port for the local OAuth callback server.
const DefaultHTTPTimeout = 30 * time.Second
DefaultHTTPTimeout is the default timeout for HTTP requests.
const MetadataCacheTTL = 1 * time.Hour
MetadataCacheTTL is the TTL for cached OAuth metadata. This allows the cache to refresh periodically in case server configuration changes.
const SilentAuthTimeout = 15 * time.Second
SilentAuthTimeout is the timeout for silent re-authentication attempts. This is shorter than the interactive timeout since silent auth should complete within seconds (it's just a browser redirect, no user interaction).
Variables ¶
var ErrAuthRequired = errors.New("authentication required")
ErrAuthRequired is returned when OAuth authentication is required.
Functions ¶
func IsTokenExpiredError ¶
IsTokenExpiredError checks if an error indicates that the OAuth token has expired. This is used to detect 401 errors with token validation failures so that automatic re-authentication can be triggered.
The function checks for common patterns in error messages that indicate authentication failures, including HTTP 401 status codes and OAuth-specific error codes like "invalid_token".
Security note: Patterns are designed to be specific to avoid false positives that could trigger unnecessary re-authentication flows.
func OpenBrowser ¶
OpenBrowser opens the specified URL in the default web browser. It supports Linux, macOS, and Windows.
Security: Only HTTP and HTTPS URLs are allowed to prevent command injection attacks through malicious URL schemes.
Returns an error if:
- The URL is invalid or empty
- The URL scheme is not http or https
- The browser could not be opened
- The platform is not supported
Types ¶
type AuthFlow ¶
type AuthFlow struct {
// ServerURL is the URL of the Muster server we're authenticating to.
ServerURL string
// IssuerURL is the OAuth issuer URL.
IssuerURL string
// PKCE holds the PKCE challenge parameters.
PKCE *pkgoauth.PKCEChallenge
// State is the OAuth state parameter.
State string
// CallbackServer is the local HTTP server waiting for the callback.
CallbackServer *CallbackServer
// Metadata is the discovered OAuth metadata.
Metadata *OAuthMetadata
// StartedAt is when the flow was initiated.
StartedAt time.Time
// Options contains the flow options (silent mode, login hint, etc.)
Options *AuthFlowOptions
}
AuthFlow represents an in-progress OAuth authorization flow.
type AuthFlowOptions ¶
type AuthFlowOptions struct {
// Silent enables prompt=none for silent re-authentication.
// The IdP will not show any UI; if the user doesn't have an active session,
// an error is returned instead of showing a login page.
Silent bool
// LoginHint pre-fills the email/username field at the IdP.
// Useful for re-authentication when the user's identity is already known.
LoginHint string
// IDTokenHint is a previously issued ID token as a hint about the user's session.
// Used with Silent=true to identify the user for silent re-authentication.
IDTokenHint string
}
AuthFlowOptions configures the authorization flow behavior. These options control OIDC parameters per OpenID Connect Core 1.0 Section 3.1.2.1.
type AuthManager ¶
type AuthManager struct {
// contains filtered or unexported fields
}
AuthManager manages OAuth authentication for the Muster Agent. It handles 401 detection, auth flow orchestration, and state transitions.
func NewAuthManager ¶
func NewAuthManager(cfg AuthManagerConfig) (*AuthManager, error)
NewAuthManager creates a new auth manager.
func (*AuthManager) CheckConnection ¶
CheckConnection attempts to connect to the server and detect auth requirements. It returns the auth state and any error that occurred.
If a 401 is received, the manager transitions to AuthStatePendingAuth and extracts the auth challenge from the WWW-Authenticate header.
func (*AuthManager) ClearToken ¶
func (m *AuthManager) ClearToken() error
ClearToken clears the stored token for the current server.
func (*AuthManager) GetAccessToken ¶
func (m *AuthManager) GetAccessToken() (string, error)
GetAccessToken returns the access token for the server. It will automatically refresh the token if it's about to expire and a refresh token is available. Returns an error if not authenticated.
func (*AuthManager) GetAuthChallenge ¶
func (m *AuthManager) GetAuthChallenge() *pkgoauth.AuthChallenge
GetAuthChallenge returns the current auth challenge (if in pending auth state).
func (*AuthManager) GetAuthURL ¶
func (m *AuthManager) GetAuthURL() string
GetAuthURL returns the authorization URL (if auth flow has been started).
func (*AuthManager) GetBearerToken ¶
func (m *AuthManager) GetBearerToken() (string, error)
GetBearerToken returns the token formatted as a Bearer authorization header value.
func (*AuthManager) GetLastError ¶
func (m *AuthManager) GetLastError() error
GetLastError returns the last error that occurred.
func (*AuthManager) GetServerURL ¶
func (m *AuthManager) GetServerURL() string
GetServerURL returns the server URL being authenticated to.
func (*AuthManager) GetState ¶
func (m *AuthManager) GetState() AuthState
GetState returns the current auth state.
func (*AuthManager) GetStoredToken ¶
func (m *AuthManager) GetStoredToken() *StoredToken
GetStoredToken returns the stored token for the current server. Returns nil if not authenticated or no token exists.
func (*AuthManager) GetStoredTokenForEndpoint ¶
func (m *AuthManager) GetStoredTokenForEndpoint(endpoint string) *StoredToken
GetStoredTokenForEndpoint returns the stored token for a specific endpoint, including expired tokens. This is used for silent re-authentication where we need the id_token from an expired session for login hints. Note: No mutex is needed here - we only use the endpoint parameter, not struct fields.
func (*AuthManager) HasValidTokenForEndpoint ¶
func (m *AuthManager) HasValidTokenForEndpoint(endpoint string) bool
HasValidTokenForEndpoint checks if a valid token exists for the given endpoint. This method checks the filesystem for tokens that may have been created by external processes (e.g., 'muster auth login' CLI command). If a valid token is found, it updates the internal auth state to AuthStateAuthenticated. This enables the agent to detect CLI-based authentication and upgrade from pending auth state.
func (*AuthManager) StartAuthFlow ¶
func (m *AuthManager) StartAuthFlow(ctx context.Context) (string, error)
StartAuthFlow initiates the OAuth authentication flow. Returns the authorization URL that the user should open in their browser. This should only be called when in AuthStatePendingAuth.
func (*AuthManager) StartAuthFlowSilent ¶
func (m *AuthManager) StartAuthFlowSilent(ctx context.Context, loginHint, idTokenHint string) (string, error)
StartAuthFlowSilent initiates a silent OAuth authentication flow using prompt=none. This attempts re-authentication without user interaction if the user has an active session at the IdP. The loginHint should be the user's email from a previous session.
If silent auth fails (user needs to log in), WaitForAuth will return an error that can be detected with mcpoauth.IsSilentAuthError(). The caller should then fall back to interactive authentication via StartAuthFlow().
This should only be called when in AuthStatePendingAuth.
func (*AuthManager) WaitForAuth ¶
func (m *AuthManager) WaitForAuth(ctx context.Context) error
WaitForAuth waits for the authentication flow to complete. This blocks until the user completes authentication or the context is cancelled.
type AuthManagerConfig ¶
type AuthManagerConfig struct {
// CallbackPort is the port for the local OAuth callback server.
CallbackPort int
// TokenStorageDir is the directory for storing tokens.
TokenStorageDir string
// FileMode enables file-based token persistence.
FileMode bool
}
AuthManagerConfig configures the auth manager.
type AuthState ¶
type AuthState int
AuthState represents the current authentication state of the agent.
const ( // AuthStateUnknown means auth state hasn't been determined yet. AuthStateUnknown AuthState = iota // AuthStateAuthenticated means we have a valid token. AuthStateAuthenticated // AuthStatePendingAuth means we received 401 and are waiting for user to authenticate. AuthStatePendingAuth // AuthStateError means authentication failed. AuthStateError )
type CallbackResult ¶
type CallbackResult struct {
// Code is the authorization code from the OAuth provider.
Code string
// State is the state parameter to verify against the original request.
State string
// Error is the error code if the authorization failed.
Error string
// ErrorDescription is a human-readable error description.
ErrorDescription string
}
CallbackResult represents the result of an OAuth callback.
func (*CallbackResult) IsError ¶
func (r *CallbackResult) IsError() bool
IsError returns true if the callback result represents an error.
type CallbackServer ¶
type CallbackServer struct {
// contains filtered or unexported fields
}
CallbackServer is a temporary local HTTP server for receiving OAuth callbacks. It starts, waits for a single callback, then shuts down.
func NewCallbackServer ¶
func NewCallbackServer(port int) *CallbackServer
NewCallbackServer creates a new callback server on the specified port. If port is 0, a random available port will be used.
func (*CallbackServer) GetPort ¶
func (s *CallbackServer) GetPort() int
GetPort returns the port the server is listening on.
func (*CallbackServer) GetRedirectURI ¶
func (s *CallbackServer) GetRedirectURI() string
GetRedirectURI returns the redirect URI for OAuth configuration.
func (*CallbackServer) Start ¶
func (s *CallbackServer) Start(ctx context.Context) (string, error)
Start starts the callback server and begins listening for the OAuth callback. The server will automatically stop when the context is cancelled. Returns the callback URL to use in the OAuth authorization request.
func (*CallbackServer) Stop ¶
func (s *CallbackServer) Stop()
Stop gracefully shuts down the callback server.
func (*CallbackServer) WaitForCallback ¶
func (s *CallbackServer) WaitForCallback(ctx context.Context) (*CallbackResult, error)
WaitForCallback waits for the OAuth callback or timeout. Returns the callback result or an error if the callback fails or times out.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is the OAuth client for the Muster Agent. It manages OAuth authentication flows for connecting to protected Muster servers.
func NewClient ¶
func NewClient(cfg ClientConfig) (*Client, error)
NewClient creates a new OAuth client with the specified configuration.
func (*Client) ClearToken ¶
ClearToken removes the stored token for a server.
func (*Client) CompleteAuthFlow ¶
func (c *Client) CompleteAuthFlow(ctx context.Context, serverURL, issuerURL string) (authURL string, waitFn func() (*oauth2.Token, error), err error)
CompleteAuthFlow is a convenience method that combines StartAuthFlow and WaitForCallback. It returns the authorization URL and a callback function to wait for completion.
func (*Client) CompleteAuthFlowWithOptions ¶
func (c *Client) CompleteAuthFlowWithOptions(ctx context.Context, serverURL, issuerURL string, opts *AuthFlowOptions) (authURL string, waitFn func() (*oauth2.Token, error), err error)
CompleteAuthFlowWithOptions is a convenience method that combines StartAuthFlowWithOptions and WaitForCallback. It returns the authorization URL and a callback function to wait for completion.
When opts.Silent is true, the flow uses prompt=none for silent re-authentication. If silent auth fails, the wait function returns an error detectable with mcpoauth.IsSilentAuthError().
func (*Client) GetCurrentFlowServerURL ¶
GetCurrentFlowServerURL returns the server URL of the current auth flow, if any.
func (*Client) GetHTTPClient ¶
GetHTTPClient returns the underlying HTTP client for reuse. This allows other components (like AuthManager) to reuse the same client for connection pooling and consistent timeout behavior.
func (*Client) GetToken ¶
GetToken retrieves a valid OAuth token for the specified server. Returns ErrAuthRequired if no valid token exists and authentication is needed.
func (*Client) HasValidToken ¶
HasValidToken checks if a valid token exists for the specified server.
func (*Client) IsFlowInProgress ¶
IsFlowInProgress returns true if an auth flow is currently in progress.
func (*Client) RefreshTokenIfNeeded ¶
RefreshTokenIfNeeded checks if the token for the server needs refreshing and refreshes it. Returns true if the token was refreshed, false if no refresh was needed. Returns an error if the refresh failed.
func (*Client) StartAuthFlow ¶
StartAuthFlow initiates an OAuth authorization flow for the specified server. Returns the authorization URL that the user should open in their browser.
The flow uses Authorization Code Grant with PKCE for maximum security. A local callback server is started to receive the OAuth callback.
func (*Client) StartAuthFlowWithOptions ¶
func (c *Client) StartAuthFlowWithOptions(ctx context.Context, serverURL, issuerURL string, opts *AuthFlowOptions) (string, error)
StartAuthFlowWithOptions initiates an OAuth authorization flow with configurable options. The options parameter controls OIDC-specific behavior like silent re-authentication.
When opts.Silent is true, the flow uses prompt=none which attempts silent re-authentication. If the user doesn't have an active IdP session, the callback will contain an error (login_required, consent_required, or interaction_required) instead of showing a login page.
Returns the authorization URL that should be opened in the browser.
func (*Client) WaitForCallback ¶
WaitForCallback waits for the OAuth callback and exchanges the code for tokens. This should be called after StartAuthFlow and after the user has authenticated.
For silent auth flows (prompt=none), if the IdP returns an error like login_required, consent_required, or interaction_required, this method returns a *mcpoauth.SilentAuthError. Callers can check for this using mcpoauth.IsSilentAuthError(err) and fall back to interactive authentication.
type ClientConfig ¶
type ClientConfig struct {
// CallbackPort is the port for the local OAuth callback server.
// Defaults to 3000 if not specified.
CallbackPort int
// TokenStoreConfig configures token storage.
TokenStoreConfig TokenStoreConfig
// HTTPClient is an optional custom HTTP client.
HTTPClient *http.Client
}
ClientConfig configures the OAuth client.
type OAuthMetadata ¶
OAuthMetadata is an alias for pkgoauth.Metadata for use in the agent.
type StoredToken ¶
type StoredToken struct {
// AccessToken is the OAuth access token.
AccessToken string `json:"access_token"`
// RefreshToken is the OAuth refresh token (if available).
RefreshToken string `json:"refresh_token,omitempty"`
// TokenType is typically "Bearer".
TokenType string `json:"token_type"`
// Expiry is when the access token expires.
Expiry time.Time `json:"expiry,omitempty"`
// IDToken is the OIDC ID token (if available).
IDToken string `json:"id_token,omitempty"`
// ServerURL is the URL of the server this token authenticates to.
ServerURL string `json:"server_url"`
// IssuerURL is the OAuth issuer that issued this token.
IssuerURL string `json:"issuer_url"`
// ClientID is the OAuth client_id that was used to obtain this token.
// This must be used when refreshing the token.
ClientID string `json:"client_id,omitempty"`
// CreatedAt is when the token was stored.
CreatedAt time.Time `json:"created_at"`
}
StoredToken represents a stored OAuth token with metadata.
func (*StoredToken) ToOAuth2Token ¶
func (t *StoredToken) ToOAuth2Token() *oauth2.Token
ToOAuth2Token converts a StoredToken to an oauth2.Token.
type TokenStore ¶
type TokenStore struct {
// contains filtered or unexported fields
}
TokenStore provides secure storage for OAuth tokens. It supports both file-based (XDG-compliant) and in-memory storage.
SECURITY: This store handles sensitive OAuth credentials. The following security measures are implemented:
- Files are created with 0600 permissions (owner read/write only)
- Storage directory is created with 0700 permissions (owner only)
- Token values are NEVER logged (only server URLs and issuers)
- Expired tokens are automatically rejected
- Token expiry includes a 60-second buffer for safety
func NewTokenStore ¶
func NewTokenStore(cfg TokenStoreConfig) (*TokenStore, error)
NewTokenStore creates a new token store with the specified configuration.
func (*TokenStore) Clear ¶
func (s *TokenStore) Clear() error
Clear removes all stored tokens (both in-memory and file-based). SECURITY: Logs bulk token clearing for audit trail.
func (*TokenStore) DeleteToken ¶
func (s *TokenStore) DeleteToken(serverURL string) error
DeleteToken removes a stored token for a specific server. SECURITY: Logs token deletion for audit trail without logging token values.
func (*TokenStore) GetByIssuer ¶
func (s *TokenStore) GetByIssuer(issuerURL string) *StoredToken
GetByIssuer retrieves a stored token for a specific issuer. This enables SSO by allowing token lookup by issuer URL rather than server URL. Returns nil if no token exists for the issuer or the token has expired.
func (*TokenStore) GetToken ¶
func (s *TokenStore) GetToken(serverURL string) *StoredToken
GetToken retrieves a stored token for a specific server. Returns nil if no token exists or the token has expired/expiring. Note: This does NOT delete expired tokens from cache to allow GetTokenIncludingExpiring to retrieve them for refresh purposes.
func (*TokenStore) GetTokenIncludingExpiring ¶
func (s *TokenStore) GetTokenIncludingExpiring(serverURL string) *StoredToken
GetTokenIncludingExpiring retrieves a stored token even if it's about to expire. This is used for token refresh - we need the refresh token even if the access token is expiring soon. Returns nil if no token exists at all.
func (*TokenStore) HasValidToken ¶
func (s *TokenStore) HasValidToken(serverURL string) bool
HasValidToken checks if a valid (non-expired) token exists for a server.
func (*TokenStore) HasValidTokenForIssuer ¶
func (s *TokenStore) HasValidTokenForIssuer(issuerURL string) bool
HasValidTokenForIssuer checks if a valid token exists for a specific issuer.
func (*TokenStore) StoreToken ¶
func (s *TokenStore) StoreToken(serverURL, issuerURL string, token *oauth2.Token) error
StoreToken stores an OAuth token for a specific server. SECURITY: Token values are never logged. Only server/issuer URLs are logged for audit purposes.
type TokenStoreConfig ¶
type TokenStoreConfig struct {
// StorageDir is the directory for storing token files.
// Defaults to ~/.config/muster/tokens
StorageDir string
// FileMode enables file-based persistence. If false, tokens are in-memory only.
FileMode bool
}
TokenStoreConfig configures the token store.