oauth

package
v0.4.3 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

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

View Source
const CallbackTimeout = 10 * time.Minute

CallbackTimeout is how long to wait for the OAuth callback.

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

View Source
const DefaultCallbackPort = 3000

DefaultCallbackPort is the default port for the local OAuth callback server.

View Source
const DefaultHTTPTimeout = 30 * time.Second

DefaultHTTPTimeout is the default timeout for HTTP requests.

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

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

View Source
var ErrAuthRequired = errors.New("authentication required")

ErrAuthRequired is returned when OAuth authentication is required.

Functions

func OpenBrowser

func OpenBrowser(urlStr string) error

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 AgentTokenStore added in v0.0.231

type AgentTokenStore struct {
	// contains filtered or unexported fields
}

AgentTokenStore is a thin context-binder that implements mcp-go's transport.TokenStore interface by binding a server URL to the agent's file-based TokenStore.

It has no storage of its own -- all reads and writes go through the underlying TokenStore. The only local state is a cached copy of the ID token, because mcp-go's transport.Token doesn't track ID tokens.

mcp-go owns token refresh and 401 handling. This store returns the current token as-is and persists whatever mcp-go writes back after a successful refresh.

func NewAgentTokenStore added in v0.0.231

func NewAgentTokenStore(serverURL string, tokenStore *TokenStore) *AgentTokenStore

NewAgentTokenStore creates a new token store that binds the given server URL to the agent's file-based token store.

func SetupOAuthConfig added in v0.0.231

func SetupOAuthConfig(serverURL string) (*transport.OAuthConfig, *AgentTokenStore, error)

SetupOAuthConfig creates an AgentTokenStore and returns the OAuthConfig for use with mcp-go's WithHTTPOAuth / WithOAuth transport options. This is the standard way to configure agent/CLI clients for OAuth authentication.

The returned AgentTokenStore wraps the file-based token store at ~/.config/muster/tokens/, so tokens stored by `muster auth login` are automatically picked up by the transport.

func SetupOAuthConfigWithDir added in v0.0.231

func SetupOAuthConfigWithDir(serverURL, tokenStorageDir string) (*transport.OAuthConfig, *AgentTokenStore, error)

SetupOAuthConfigWithDir creates an AgentTokenStore with a custom storage directory. If tokenStorageDir is empty, defaults to ~/.config/muster/tokens/.

func (*AgentTokenStore) GetIDToken added in v0.0.231

func (s *AgentTokenStore) GetIDToken() string

GetIDToken returns the last cached ID token. mcp-go's transport.Token doesn't track ID tokens, so we cache them from the file store on each GetToken() call for SSO forwarding.

func (*AgentTokenStore) GetToken added in v0.0.231

func (s *AgentTokenStore) GetToken(ctx context.Context) (*transport.Token, error)

GetToken returns the current OAuth token from the file-based store. Returns transport.ErrNoToken when no token is available, which signals mcp-go to initiate the OAuth authorization flow.

func (*AgentTokenStore) SaveToken added in v0.0.231

func (s *AgentTokenStore) SaveToken(ctx context.Context, token *transport.Token) error

SaveToken persists a refreshed token to the file-based store. mcp-go calls this after a successful token refresh.

The cached IDToken is preserved because refresh responses typically don't include ID tokens.

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

func (m *AuthManager) CheckConnection(ctx context.Context, serverURL string) (AuthState, error)

CheckConnection checks whether the agent has a valid token for the server. If no valid token exists, probes the server to discover OAuth auth requirements.

Returns:

  • AuthStateAuthenticated if a valid token exists in the file store
  • AuthStatePendingAuth if auth is required (authChallenge will be populated)
  • AuthStateUnknown if the server doesn't require auth or can't be reached

func (*AuthManager) ClearToken

func (m *AuthManager) ClearToken() error

ClearToken clears the stored token for the current server.

func (*AuthManager) Close

func (m *AuthManager) Close() error

Close cleans up resources.

func (*AuthManager) GetAccessToken

func (m *AuthManager) GetAccessToken() (string, error)

GetAccessToken returns the access token for the server. Token refresh is handled by mcp-go's transport layer, so this method simply reads the current token from the store.

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) HasCredentials added in v0.1.1

func (m *AuthManager) HasCredentials(endpoint string) bool

HasCredentials reports whether usable credentials exist for the endpoint: either a non-expired access token or an expired token paired with a refresh token. Unlike HasValidTokenForEndpoint this does not update internal auth state because the token may still need to be refreshed.

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
)

func (AuthState) String

func (s AuthState) String() string

String returns the string representation of the auth state.

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

	// Iss is the RFC 9207 issuer identifier returned in the authorization
	// response. Non-conforming servers omit it; callers must treat empty
	// as "not advertised" rather than "did not match".
	Iss 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

func (c *Client) ClearToken(serverURL string) error

ClearToken removes the stored token for a server.

func (*Client) Close

func (c *Client) Close() error

Close cleans up the OAuth client resources.

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

func (c *Client) GetCurrentFlowServerURL() string

GetCurrentFlowServerURL returns the server URL of the current auth flow, if any.

func (*Client) GetHTTPClient

func (c *Client) GetHTTPClient() *http.Client

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

func (c *Client) GetToken(serverURL string) (*oauth2.Token, error)

GetToken retrieves a valid OAuth token for the specified server. Returns ErrAuthRequired if no valid token exists and authentication is needed.

func (*Client) HasCredentials added in v0.1.1

func (c *Client) HasCredentials(serverURL string) bool

HasCredentials reports whether usable credentials exist for the server: either a valid access token or an expired token with a refresh token that the mcp-go transport can use to obtain a new access token.

func (*Client) HasValidToken

func (c *Client) HasValidToken(serverURL string) bool

HasValidToken checks if a valid token exists for the specified server.

func (*Client) IsFlowInProgress

func (c *Client) IsFlowInProgress() bool

IsFlowInProgress returns true if an auth flow is currently in progress.

func (*Client) StartAuthFlow

func (c *Client) StartAuthFlow(ctx context.Context, serverURL, issuerURL string) (string, error)

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

func (c *Client) WaitForCallback(ctx context.Context) (*oauth2.Token, error)

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

type OAuthMetadata = pkgoauth.Metadata

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) HasCredentials added in v0.1.1

func (s *TokenStore) HasCredentials(serverURL string) bool

HasCredentials checks whether the store holds credentials that the mcp-go transport can use: either a non-expired access token or an expired token that carries a refresh token.

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.

Jump to

Keyboard shortcuts

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