Documentation
¶
Overview ¶
Package mcpauth implements downstream OAuth 2.1 brokering for external MCP servers: authorization-server discovery (RFC 9728 / RFC 8414), dynamic client registration (RFC 7591), the authorization-code + PKCE flow, and encrypted token persistence with refresh rotation.
The name avoids "auth" to keep it distinct from the gateway's inbound API authentication (internal/api/auth.go).
Index ¶
- Constants
- Variables
- type Broker
- func (b *Broker) BeginAuthorization(ctx context.Context, server string, timeout time.Duration) (authorizeURL, authState string, err error)
- func (b *Broker) CallbackHandler() http.Handler
- func (b *Broker) CompleteAuthorization(ctx context.Context, stateToken, code, iss string) error
- func (b *Broker) CompleteManual(ctx context.Context, redirectURL string) error
- func (b *Broker) Configure(server, serverURL string, auth *mcp.ServerAuthConfig) error
- func (b *Broker) Deconfigure(server string)
- func (b *Broker) FailAuthorization(stateToken string, cause error)
- func (b *Broker) HeaderSource(server string) mcp.HeaderSource
- func (b *Broker) HeaderSourceForResource(serverURL string) mcp.HeaderSource
- func (b *Broker) Logout(ctx context.Context, server string) error
- func (b *Broker) RemoveServerGrant(ctx context.Context, server string) error
- func (b *Broker) Reset(ctx context.Context, server string) error
- func (b *Broker) ServerStatus(server string) (ServerAuthInfo, error)
- func (b *Broker) SetOnAuthorized(fn func(server string))
- func (b *Broker) SetRedactor(fn func(values []string))
- func (b *Broker) SetStateSink(sink StateSink)
- func (b *Broker) Status() []ServerAuthInfo
- func (b *Broker) Wait(ctx context.Context, stateToken string) error
- type ClientRegistration
- type Grant
- type ServerAuthInfo
- type StateSink
- type TokenStore
- func (s *TokenStore) DeleteGrant(resource string) error
- func (s *TokenStore) DeleteRegistration(issuer string) error
- func (s *TokenStore) Grant(resource string) (Grant, bool, error)
- func (s *TokenStore) Grants() (map[string]Grant, error)
- func (s *TokenStore) PutGrant(g Grant) error
- func (s *TokenStore) PutRegistration(r ClientRegistration) error
- func (s *TokenStore) Registration(issuer string) (ClientRegistration, bool, error)
Constants ¶
const CallbackPath = "/oauth/callback"
CallbackPath is the route the broker's redirect URI points at. It is mounted on the gateway's existing listener, outside the inbound API auth middleware: the callback authenticates via the single-use state parameter, and the browser performing the redirect holds no gateway bearer token.
const DefaultAuthTimeout = 5 * time.Minute
DefaultAuthTimeout bounds how long a pending authorization waits for the browser callback before it expires with no partial state.
Variables ¶
var ErrDCRUnsupported = errors.New(
"authorization server does not support dynamic client registration; " +
"set auth.client_id (and auth.client_secret if issued) in stack.yaml")
ErrDCRUnsupported reports an authorization server that refuses dynamic client registration. The message names the fix because it is shown to the user verbatim.
Functions ¶
This section is empty.
Types ¶
type Broker ¶
type Broker struct {
// contains filtered or unexported fields
}
Broker owns downstream OAuth state: discovery, client identity, the authorization-code + PKCE flow, token persistence, and refresh rotation.
func NewBroker ¶
func NewBroker(store *TokenStore, redirectURL string, logger *slog.Logger) *Broker
NewBroker constructs a broker persisting into store, with the given daemon callback URL.
func (*Broker) BeginAuthorization ¶
func (b *Broker) BeginAuthorization(ctx context.Context, server string, timeout time.Duration) (authorizeURL, authState string, err error)
BeginAuthorization starts the authorization-code flow for a configured server: discovery, client identity resolution (static config, cached registration, then DCR), PKCE, and the composed authorization URL. It returns the URL for the caller to open (the broker never opens a browser) and the single-use state that keys the pending flow.
func (*Broker) CallbackHandler ¶
CallbackHandler returns the HTTP handler for the authorization redirect. It completes the pending flow and renders a small self-closing page; all real error detail stays on the CLI/API waiter side.
func (*Broker) CompleteAuthorization ¶
CompleteAuthorization redeems an authorization code delivered to the callback (or pasted manually). state is single-use; iss, when the AS returned one, must match the recorded issuer (RFC 9207).
func (*Broker) CompleteManual ¶
CompleteManual accepts a full pasted redirect URL (the --manual path for SSH sessions where the loopback callback cannot reach the daemon).
func (*Broker) Configure ¶
func (b *Broker) Configure(server, serverURL string, auth *mcp.ServerAuthConfig) error
Configure registers (or updates) the OAuth config for a server and reflects its current state into the sink: authorized when a usable grant already exists for the resource, needs-auth otherwise.
func (*Broker) Deconfigure ¶
Deconfigure removes a server from the broker without touching stored grants (other stacks may share them; RemoveServerGrant handles cleanup).
func (*Broker) FailAuthorization ¶
FailAuthorization resolves a pending flow with an error (an AS denial such as access_denied), waking any waiter immediately instead of letting it run out the flow timeout. Unknown or already-resolved states are a no-op.
func (*Broker) HeaderSource ¶
func (b *Broker) HeaderSource(server string) mcp.HeaderSource
HeaderSource returns the live token source for a configured server. The same instance is reused so its cached token survives across requests.
func (*Broker) HeaderSourceForResource ¶
func (b *Broker) HeaderSourceForResource(serverURL string) mcp.HeaderSource
HeaderSourceForResource returns a header source bound to a raw resource URL rather than a configured server name. The probe uses it so a pre-apply token acquired in the wizard carries over to the applied server (both key by canonical resource URL).
func (*Broker) Logout ¶
Logout revokes (best effort, RFC 7009) and deletes the grant backing a configured server.
func (*Broker) RemoveServerGrant ¶
RemoveServerGrant is the stack-removal cleanup path: it revokes and deletes the grant only when no other configured server shares the resource.
func (*Broker) Reset ¶
Reset deletes the grant and the issuer's cached client registration for a server: the first-class version of mcp-remote's rm -rf escape hatch.
func (*Broker) ServerStatus ¶
func (b *Broker) ServerStatus(server string) (ServerAuthInfo, error)
ServerStatus returns authorization info for one configured server.
func (*Broker) SetOnAuthorized ¶
SetOnAuthorized installs a callback fired after a successful authorization; the controller uses it to re-register the server.
func (*Broker) SetRedactor ¶
SetRedactor installs the log-redaction hook. Every access and refresh token is registered the moment it enters memory, before any code path can log a request or response containing it.
func (*Broker) SetStateSink ¶
SetStateSink installs the gateway hook that receives state transitions.
func (*Broker) Status ¶
func (b *Broker) Status() []ServerAuthInfo
Status returns per-server authorization info for every configured server.
type ClientRegistration ¶
type ClientRegistration struct {
Issuer string `json:"issuer"`
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret,omitempty"`
RedirectURI string `json:"redirectUri"`
CreatedAt time.Time `json:"createdAt"`
}
ClientRegistration is a dynamically registered OAuth client, keyed by issuer (SEP-2352: credentials are bound to the issuing AS and never reused with a different one).
type Grant ¶
type Grant struct {
Resource string `json:"resource"` // canonical resource URL
Issuer string `json:"issuer"` // authorization server issuer
Scopes []string `json:"scopes,omitempty"` // granted scopes
Token *oauth2.Token `json:"token"` // access + refresh token
UpdatedAt time.Time `json:"updatedAt"`
// Endpoints captured at authorization time so refresh and revocation
// work across daemon restarts without re-running discovery.
TokenEndpoint string `json:"tokenEndpoint"`
RevocationEndpoint string `json:"revocationEndpoint,omitempty"`
}
Grant is one authorization against a downstream server, keyed by the canonical resource URL. Two stacks pointing at the same endpoint share a grant, and renaming a server in stack.yaml does not force a re-login.
type ServerAuthInfo ¶
type ServerAuthInfo struct {
Server string `json:"server"`
Resource string `json:"resource"`
Status string `json:"status"` // mcp.AuthStatusAuthorized | mcp.AuthStatusNeedsAuth
Issuer string `json:"issuer,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Expiry *time.Time `json:"expiry,omitempty"`
}
ServerAuthInfo is the per-server view returned to the CLI and API.
type StateSink ¶
type StateSink interface {
SetServerAuthState(name string, st mcp.ServerAuthState)
}
StateSink receives authorization state transitions; *mcp.Gateway satisfies it. A nil sink is valid (probe-only broker use).
type TokenStore ¶
type TokenStore struct {
// contains filtered or unexported fields
}
TokenStore persists OAuth grants and client registrations encrypted at rest under the gridctl state directory. All operations take a cross- process flock and re-read the file, so a CLI-driven authorization is observed by the running daemon on its next access.
func NewTokenStore ¶
func NewTokenStore(dir string) (*TokenStore, error)
NewTokenStore opens (creating if needed) the token store rooted at dir. When dir is empty, <state base dir>/oauth is used.
func (*TokenStore) DeleteGrant ¶
func (s *TokenStore) DeleteGrant(resource string) error
DeleteGrant removes the grant for a resource. Missing grants are a no-op.
func (*TokenStore) DeleteRegistration ¶
func (s *TokenStore) DeleteRegistration(issuer string) error
DeleteRegistration removes the registered client for an issuer.
func (*TokenStore) Grant ¶
func (s *TokenStore) Grant(resource string) (Grant, bool, error)
Grant returns the stored grant for a canonical resource URL.
func (*TokenStore) Grants ¶
func (s *TokenStore) Grants() (map[string]Grant, error)
GrantsByIssuerResource reports how many stored grants share the given issuer and resource; used for the cross-stack refcount before revoking on server removal.
func (*TokenStore) PutGrant ¶
func (s *TokenStore) PutGrant(g Grant) error
PutGrant stores (or replaces) the grant for its resource. Called on initial authorization and again on every refresh-token rotation.
func (*TokenStore) PutRegistration ¶
func (s *TokenStore) PutRegistration(r ClientRegistration) error
PutRegistration stores a registered client keyed by issuer.
func (*TokenStore) Registration ¶
func (s *TokenStore) Registration(issuer string) (ClientRegistration, bool, error)
Registration returns the stored client registration for an issuer.