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: 19 Imported by: 0

Documentation

Overview

Package oauth provides shared OAuth 2.1 types and utilities used by both the Muster Agent and Server.

This package contains the common OAuth functionality that is shared between the agent-side OAuth implementation (internal/agent/oauth) and the server-side OAuth implementation (internal/oauth).

Core Components

  • Token: OAuth token representation with expiry checking
  • Metadata: OAuth/OIDC server metadata (RFC 8414)
  • AuthChallenge: Parsed WWW-Authenticate header information
  • PKCE: Proof Key for Code Exchange generation (RFC 7636)
  • Client: OAuth client for metadata discovery and token operations

Usage

Both the agent and server import this package for shared types and utilities, then wrap it with their specific storage backends and UI handling.

Agent usage (file-based storage, browser opening):

import "github.com/giantswarm/muster/pkg/oauth"

challenge, err := oauth.ParseWWWAuthenticate(header)
pkce, err := oauth.GeneratePKCE() // Uses golang.org/x/oauth2 under the hood

Server usage (in-memory storage, HTTP callbacks):

import "github.com/giantswarm/muster/pkg/oauth"

client := oauth.NewClient(httpClient, logger)
metadata, err := client.DiscoverMetadata(ctx, issuer)

Index

Constants

View Source
const (
	// DefaultHTTPTimeout is the default timeout for HTTP requests.
	DefaultHTTPTimeout = 30 * time.Second

	// DefaultMetadataCacheTTL is the default TTL for cached OAuth metadata.
	DefaultMetadataCacheTTL = 30 * time.Minute
)
View Source
const DefaultExpiryMargin = 30 * time.Second

DefaultExpiryMargin is the default margin when checking token expiry. This accounts for clock skew and network latency.

View Source
const DefaultSessionDuration = 30 * 24 * time.Hour

DefaultSessionDuration is the expected maximum session duration before re-authentication is required. This should match the server-side RefreshTokenTTL and is used by the CLI to estimate session expiry from the stored token's CreatedAt timestamp. Aligned with Dex's absoluteLifetime (720h = 30 days).

View Source
const DefaultTokenStorageDir = ".config/muster/tokens"

DefaultTokenStorageDir is the default directory for storing OAuth tokens, relative to the user's home directory. This follows XDG conventions. This constant is shared across all OAuth implementations for consistency.

View Source
const (
	// DisplayAuthRequired is the formatted string shown in CLI prompts when servers require authentication.
	// This is displayed prominently in uppercase because it requires user action (running 'auth login').
	// Example prompt: "muster staging [AUTH REQUIRED] > "
	DisplayAuthRequired = "[AUTH REQUIRED]"
)

Display constants for user-facing output. These are formatted strings suitable for CLI prompts and status displays.

Variables

This section is empty.

Functions

func DefaultTokenDir added in v0.1.5

func DefaultTokenDir() (string, error)

DefaultTokenDir returns the absolute path to the default token storage directory (~/.config/muster/tokens). It does not create the directory; callers that need it to exist should call os.MkdirAll themselves.

func GeneratePKCERaw

func GeneratePKCERaw() (verifier, challenge string)

GeneratePKCERaw generates a PKCE code verifier and challenge as raw strings. This is useful when you don't need the full PKCEChallenge struct.

This implementation uses the standard golang.org/x/oauth2 library which provides RFC 7636 compliant PKCE generation.

Returns the verifier and S256 challenge. This function cannot fail as it uses the standard library's implementation.

func GenerateState

func GenerateState() (string, error)

GenerateState generates a random state parameter for OAuth. The state is used to prevent CSRF attacks and link the authorization response back to the original request.

Returns a base64url-encoded random string.

func IsOAuthUnauthorizedError added in v0.0.231

func IsOAuthUnauthorizedError(err error) bool

IsOAuthUnauthorizedError checks if an error indicates an OAuth authorization failure using mcp-go's typed error detection. Returns true for both transport.OAuthAuthorizationRequiredError (when WithHTTPOAuth is configured) and transport.ErrUnauthorized (bare 401 without OAuth handler).

func NormalizeServerURL

func NormalizeServerURL(serverURL string) string

NormalizeServerURL normalizes a server URL by stripping transport-specific path suffixes (/mcp, /sse) and trailing slashes to get the base server URL. This ensures consistent token storage and OAuth metadata discovery regardless of which endpoint path is used when connecting.

This function is shared across all OAuth implementations for consistency.

Types

type AuthChallenge

type AuthChallenge struct {
	// Scheme is the authentication scheme (typically "Bearer" for OAuth 2.0).
	Scheme string

	// Realm is the protection realm (often the authorization server name or URL).
	Realm string

	// Issuer is the OAuth/OIDC issuer URL.
	// This may be derived from the Realm if it's a URL.
	Issuer string

	// ResourceMetadataURL is the URL to the protected resource metadata.
	// This follows RFC 9728 for OAuth 2.0 Protected Resource Metadata.
	ResourceMetadataURL string

	// Scope is the space-separated list of required OAuth scopes.
	Scope string

	// Error is the error code from the WWW-Authenticate header (if any).
	Error string

	// ErrorDescription is a human-readable error description (if any).
	ErrorDescription string
}

AuthChallenge represents parsed information from a WWW-Authenticate header. This contains the OAuth server metadata needed to initiate the auth flow.

func ParseWWWAuthenticate

func ParseWWWAuthenticate(header string) (*AuthChallenge, error)

ParseWWWAuthenticate parses a WWW-Authenticate header value. It supports the Bearer scheme with OAuth 2.0 and MCP-specific parameters.

Example headers:

Bearer realm="https://auth.example.com"
Bearer realm="https://auth.example.com", scope="openid profile"
Bearer realm="https://auth.example.com", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

Returns an AuthChallenge with the parsed parameters, or an error if parsing fails.

func (*AuthChallenge) GetIssuer

func (c *AuthChallenge) GetIssuer() string

GetIssuer returns the OAuth issuer URL. It prefers the explicit Issuer field, falls back to Realm if it's a URL.

func (*AuthChallenge) IsOAuthChallenge

func (c *AuthChallenge) IsOAuthChallenge() bool

IsOAuthChallenge returns true if this represents an OAuth authentication challenge.

type AuthRequiredInfo

type AuthRequiredInfo struct {
	Server   string `json:"server"`
	Issuer   string `json:"issuer"`
	Scope    string `json:"scope,omitempty"`
	AuthTool string `json:"auth_tool"` // Always "core_auth_login" per ADR-008
}

AuthRequiredInfo contains information about a server requiring authentication. This is a simplified view used by the agent to build human-readable notifications.

Per ADR-008, AuthTool is always "core_auth_login" - callers can use this tool with the Server field as the argument to authenticate.

type AuthStatusResponse

type AuthStatusResponse struct {
	Servers []ServerAuthStatus `json:"servers"`
}

AuthStatusResponse is the structured response from the auth://status MCP resource. It provides the AI with complete information about which servers need authentication. This type is shared between the aggregator (producer) and agent (consumer).

type Client

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

Client handles OAuth 2.1 protocol operations. It provides metadata discovery, token exchange, and token refresh.

func NewClient

func NewClient(opts ...ClientOption) *Client

NewClient creates a new OAuth client.

func (*Client) BuildAuthorizationURL

func (c *Client) BuildAuthorizationURL(authEndpoint, clientID, redirectURI, state, scope string, pkce *PKCEChallenge) (string, error)

BuildAuthorizationURL constructs an OAuth authorization URL.

func (*Client) ClearMetadataCache

func (c *Client) ClearMetadataCache()

ClearMetadataCache clears the metadata cache. Useful for testing or when metadata needs to be refreshed immediately.

func (*Client) DiscoverMetadata

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

DiscoverMetadata fetches OAuth metadata from the issuer's well-known endpoint. It tries RFC 8414 (/.well-known/oauth-authorization-server) first, then falls back to OpenID Connect (/.well-known/openid-configuration).

Results are cached with a TTL to reduce network requests.

func (*Client) ExchangeCode

func (c *Client) ExchangeCode(ctx context.Context, tokenEndpoint, code, redirectURI, clientID, codeVerifier string) (*Token, error)

ExchangeCode exchanges an authorization code for tokens.

type ClientMetadata

type ClientMetadata struct {
	ClientID                string   `json:"client_id"`
	ClientName              string   `json:"client_name,omitempty"`
	ClientURI               string   `json:"client_uri,omitempty"`
	RedirectURIs            []string `json:"redirect_uris"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	ResponseTypes           []string `json:"response_types,omitempty"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method,omitempty"`
	Scope                   string   `json:"scope,omitempty"`
	LogoURI                 string   `json:"logo_uri,omitempty"`
	PolicyURI               string   `json:"policy_uri,omitempty"`
	TermsOfServiceURI       string   `json:"tos_uri,omitempty"`
	SoftwareID              string   `json:"software_id,omitempty"`
	SoftwareVersion         string   `json:"software_version,omitempty"`
}

ClientMetadata represents OAuth 2.0 Client Metadata as defined in RFC 7591. Used for Client ID Metadata Documents (CIMD) in MCP OAuth.

type ClientOption

type ClientOption func(*Client)

ClientOption configures the OAuth client.

func WithHTTPClient

func WithHTTPClient(httpClient *http.Client) ClientOption

WithHTTPClient sets a custom HTTP client.

func WithMetadataCacheTTL

func WithMetadataCacheTTL(ttl time.Duration) ClientOption

WithMetadataCacheTTL sets the metadata cache TTL.

type IDTokenClaims

type IDTokenClaims struct {
	// Subject is the unique user identifier (sub claim).
	Subject string `json:"sub"`
	// Email is the user's email address (email claim).
	Email string `json:"email"`
}

IDTokenClaims holds the identity claims extracted from JWT ID tokens. This is used to display user identity information (subject, email) from OAuth authentication without requiring full JWT validation.

type Metadata

type Metadata struct {
	// Issuer is the authorization server's issuer identifier.
	Issuer string `json:"issuer"`

	// AuthorizationEndpoint is the URL of the authorization endpoint.
	AuthorizationEndpoint string `json:"authorization_endpoint"`

	// TokenEndpoint is the URL of the token endpoint.
	TokenEndpoint string `json:"token_endpoint"`

	// UserinfoEndpoint is the URL of the userinfo endpoint (OIDC).
	UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`

	// JwksURI is the URL of the JSON Web Key Set.
	JwksURI string `json:"jwks_uri,omitempty"`

	// RegistrationEndpoint is the URL for dynamic client registration.
	RegistrationEndpoint string `json:"registration_endpoint,omitempty"`

	// ScopesSupported lists the OAuth 2.0 scope values supported.
	ScopesSupported []string `json:"scopes_supported,omitempty"`

	// ResponseTypesSupported lists the response_type values supported.
	ResponseTypesSupported []string `json:"response_types_supported,omitempty"`

	// GrantTypesSupported lists the grant types supported.
	GrantTypesSupported []string `json:"grant_types_supported,omitempty"`

	// TokenEndpointAuthMethodsSupported lists the client authentication methods.
	TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`

	// CodeChallengeMethodsSupported lists the PKCE code challenge methods.
	CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported,omitempty"`
}

Metadata represents OAuth 2.0 Authorization Server Metadata as defined in RFC 8414.

func (*Metadata) SupportsPKCE

func (m *Metadata) SupportsPKCE() bool

SupportsPKCE returns true if the server supports S256 PKCE.

type PKCEChallenge

type PKCEChallenge struct {
	// CodeVerifier is the cryptographically random string (32-96 bytes, base64url-encoded).
	// This is kept secret and never transmitted to the authorization server.
	CodeVerifier string

	// CodeChallenge is the SHA256 hash of the verifier (base64url-encoded).
	// This is sent in the authorization request.
	CodeChallenge string

	// CodeChallengeMethod is always "S256" for security (plain is not allowed in OAuth 2.1).
	CodeChallengeMethod string
}

PKCEChallenge represents a PKCE (Proof Key for Code Exchange) challenge. PKCE is required for OAuth 2.1 public clients to prevent authorization code interception.

func GeneratePKCE

func GeneratePKCE() (*PKCEChallenge, error)

GeneratePKCE generates a new PKCE code verifier and challenge. The code verifier is 32 random bytes (256 bits), base64url-encoded. The code challenge is the S256 (SHA256) hash of the verifier.

This implementation delegates to the standard golang.org/x/oauth2 library for RFC 7636 compliant PKCE generation.

Returns a PKCEChallenge ready for use in an authorization request.

type ServerAuthStatus

type ServerAuthStatus struct {
	Name     string              `json:"name"`
	Status   SessionServerStatus `json:"status"` // "connected", "auth_required", "reauth_required", "sso_pending", "disconnected", "error"
	Issuer   string              `json:"issuer,omitempty"`
	Scope    string              `json:"scope,omitempty"`
	AuthTool string              `json:"auth_tool,omitempty"` // "core_auth_login" for non-SSO servers; empty for SSO servers (per ADR-008)
	Error    string              `json:"error,omitempty"`

	// TokenForwardingEnabled indicates this server uses SSO via ID token forwarding.
	// When true, muster forwards its own ID token (from muster's OAuth server protection)
	// to this downstream server, rather than requiring a separate OAuth flow.
	TokenForwardingEnabled bool `json:"token_forwarding_enabled,omitempty"`

	// TokenExchangeEnabled indicates this server uses SSO via RFC 8693 Token Exchange.
	// When true, muster exchanges its local token for one valid on the remote cluster's
	// Identity Provider (e.g., Dex). This enables cross-cluster SSO when clusters have
	// separate Dex instances. Token exchange takes precedence over token forwarding.
	TokenExchangeEnabled bool `json:"token_exchange_enabled,omitempty"`

	// SSOAttemptFailed indicates that SSO authentication was attempted but failed.
	// This occurs when token forwarding is enabled but the downstream server
	// rejected the forwarded token (e.g., audience mismatch, token expired).
	// When true, the status will be "auth_required" and users should check
	// server trust configuration.
	SSOAttemptFailed bool `json:"sso_attempt_failed,omitempty"`
}

ServerAuthStatus represents the authentication status of a single MCP server. The Issuer field enables SSO detection - servers with the same issuer can share auth.

SSO in muster has two mechanisms:

  • Token Forwarding: When TokenForwardingEnabled is true, muster forwards its own ID token to the downstream server (requires forwardToken: true in MCPServer config).
  • Token Exchange: When TokenExchangeEnabled is true, muster exchanges its token for one valid on the remote cluster's IdP (for cross-cluster SSO).

type SessionServerStatus added in v0.1.72

type SessionServerStatus string

SessionServerStatus represents the per-user session authentication status of an MCP server. This is distinct from api.ServiceState which tracks global server lifecycle state.

const (
	// SessionServerStatusConnected indicates the server is connected and operational.
	// Tools from this server are available for use.
	SessionServerStatusConnected SessionServerStatus = "connected"

	// SessionServerStatusAuthRequired indicates the server requires OAuth authentication.
	// This is an important status that requires user action: run 'muster auth login --server <name>'.
	// The server is reachable but needs authentication before tools become available.
	//
	// This status is more informative than a generic "Disconnected" because it tells
	// users exactly what action is needed to restore connectivity.
	SessionServerStatusAuthRequired SessionServerStatus = "auth_required"

	// SessionServerStatusDisconnected indicates the server is disconnected.
	// The connection was previously established but is no longer active.
	SessionServerStatusDisconnected SessionServerStatus = "disconnected"

	// SessionServerStatusError indicates the server encountered an error.
	// Check the error field for details about what went wrong.
	SessionServerStatusError SessionServerStatus = "error"

	// SessionServerStatusUnreachable indicates the server endpoint cannot be reached.
	// This is distinct from auth_required - unreachable means network/connectivity failure,
	// not an authentication issue. Users should not be prompted to authenticate
	// for unreachable servers.
	SessionServerStatusUnreachable SessionServerStatus = "unreachable"

	// SessionServerStatusFailed indicates a session-level failure (e.g., connection dropped,
	// unexpected error during communication). This is distinct from infrastructure
	// failures (tracked in MCPServer Phase) and auth failures (tracked in AuthStatus).
	SessionServerStatusFailed SessionServerStatus = "failed"

	// SessionServerStatusSSOPending indicates that SSO authentication is in progress for this
	// server. The server is SSO-enabled and muster is currently establishing the
	// connection via token forwarding or token exchange. This is a transient state
	// that will resolve to "connected" (on success) or "auth_required" with
	// sso_attempt_failed=true (on failure).
	//
	// Clients should NOT call core_auth_login for servers in this state -- they
	// should wait for the SSO process to complete.
	SessionServerStatusSSOPending SessionServerStatus = "sso_pending"

	// SessionServerStatusReauthRequired indicates the user's session has a broken
	// upstream token refresh chain (e.g., Dex -> GitHub returned 401). The ID token
	// has expired or is no longer available, so SSO connections cannot be maintained.
	// The user must re-authenticate via 'muster auth login' to restore SSO access.
	// This is distinct from auth_required (initial auth needed) -- reauth_required
	// means a previously working session has degraded.
	SessionServerStatusReauthRequired SessionServerStatus = "reauth_required"
)

Session server status constants for use in ServerAuthStatus.Status field. These are the primary status values visible to users and AI assistants.

type Token

type Token struct {
	// AccessToken is the bearer token used for authorization.
	AccessToken string `json:"access_token"`

	// TokenType is typically "Bearer".
	TokenType string `json:"token_type,omitempty"`

	// RefreshToken is used to obtain new access tokens (optional).
	RefreshToken string `json:"refresh_token,omitempty"`

	// ExpiresIn is the token lifetime in seconds (from token response).
	ExpiresIn int `json:"expires_in,omitempty"`

	// ExpiresAt is the calculated expiration timestamp.
	ExpiresAt time.Time `json:"expires_at,omitempty"`

	// Scope is the granted scope(s), space-separated.
	Scope string `json:"scope,omitempty"`

	// Issuer is the token issuer (Identity Provider URL).
	Issuer string `json:"issuer,omitempty"`

	// IDToken is the OIDC ID token (if available).
	IDToken string `json:"id_token,omitempty"`
}

Token represents an OAuth access token with associated metadata.

func (*Token) IsExpired

func (t *Token) IsExpired() bool

IsExpired checks if the token has expired. Returns true if the token is expired or will expire within the given margin.

func (*Token) IsExpiredWithMargin

func (t *Token) IsExpiredWithMargin(margin time.Duration) bool

IsExpiredWithMargin checks if the token has expired or will expire within the margin.

func (*Token) Scopes

func (t *Token) Scopes() []string

Scopes returns the scope as a slice of individual scopes.

func (*Token) SetExpiresAtFromExpiresIn

func (t *Token) SetExpiresAtFromExpiresIn()

SetExpiresAtFromExpiresIn calculates and sets ExpiresAt from ExpiresIn.

func (*Token) ToOAuth2Token

func (t *Token) ToOAuth2Token() *oauth2.Token

ToOAuth2Token converts the Token to an oauth2.Token for compatibility with golang.org/x/oauth2.

Jump to

Keyboard shortcuts

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