Documentation
¶
Overview ¶
Package auth implements authentication: GitHub sign-in handlers, cookie sessions, and middleware that guards API routes, plus an OAuth 2.1 authorization server that lets agents (MCP clients) act for a signed-in user with bearer tokens.
Auth stores sessions, clients, and tokens through the small Store interface and reaches GitHub through OAuth, so neither storage nor the GitHub client is imported here beyond the github.User type.
Index ¶
- Constants
- Variables
- func ScopesFrom(ctx context.Context) []string
- type Auth
- func (a *Auth) AuthorizationRequest(w http.ResponseWriter, r *http.Request)
- func (a *Auth) AuthorizationServerMetadata(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Authorize(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Callback(w http.ResponseWriter, r *http.Request)
- func (a *Auth) DecideAuthorization(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Grants(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Login(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Logout(w http.ResponseWriter, r *http.Request)
- func (a *Auth) ProtectedResourceMetadata(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Register(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Require(next http.Handler) http.Handler
- func (a *Auth) RequireBearer(resource string) func(http.Handler) http.Handler
- func (a *Auth) Resource() string
- func (a *Auth) Revoke(w http.ResponseWriter, r *http.Request)
- func (a *Auth) RevokeDisallowedSessions(ctx context.Context) error
- func (a *Auth) RevokeGrant(w http.ResponseWriter, r *http.Request)
- func (a *Auth) Token(w http.ResponseWriter, r *http.Request)
- type Client
- type Code
- type Grant
- type OAuth
- type OAuthStore
- type Store
- type Token
- type User
Constants ¶
const ScopeRead = "read"
ScopeRead is the scope that grants read-only access to the MCP server.
Variables ¶
var ErrReplayed = errors.New("auth: oauth credential replayed")
ErrReplayed is returned by an OAuthStore when an authorization code or a rotated refresh token is presented again. The store has revoked the grant by then.
Functions ¶
func ScopesFrom ¶
ScopesFrom returns the scopes of the access token that Auth.RequireBearer accepted for ctx.
Types ¶
type Auth ¶
type Auth struct {
// contains filtered or unexported fields
}
Auth serves the sign-in endpoints and guards routes behind a session.
func New ¶
func New(store Store, oauth func() (OAuth, bool), baseURL string, allowedUsers []string, log *slog.Logger) *Auth
New returns an Auth that keeps sessions in store. oauth reports the GitHub client, or false until the GitHub App has been configured. baseURL is the public dashboard URL; cookies are marked Secure when it is https. allowedUsers are the GitHub logins permitted to sign in, compared without regard to case.
func (*Auth) AuthorizationRequest ¶
func (a *Auth) AuthorizationRequest(w http.ResponseWriter, r *http.Request)
AuthorizationRequest returns a pending authorization request of the signed-in user, for the consent page.
func (*Auth) AuthorizationServerMetadata ¶
func (a *Auth) AuthorizationServerMetadata(w http.ResponseWriter, r *http.Request)
AuthorizationServerMetadata serves the RFC 8414 authorization server metadata.
func (*Auth) Authorize ¶
func (a *Auth) Authorize(w http.ResponseWriter, r *http.Request)
Authorize starts an authorization: it validates the request, sends a user without a session to sign in first, and otherwise hands the request to the dashboard's consent page. Invalid requests get an error page, never a redirect to the client.
func (*Auth) Callback ¶
func (a *Auth) Callback(w http.ResponseWriter, r *http.Request)
Callback completes GitHub sign-in. If the user is permitted it creates a session and redirects to the dashboard; otherwise it redirects to the login page with an error.
func (*Auth) DecideAuthorization ¶
func (a *Auth) DecideAuthorization(w http.ResponseWriter, r *http.Request)
DecideAuthorization approves or denies a pending authorization request of the signed-in user and returns where to send the browser: the client's redirect URI with an authorization code or an access_denied error. A request can be decided once.
func (*Auth) Grants ¶
func (a *Auth) Grants(w http.ResponseWriter, r *http.Request)
Grants lists the signed-in user's connected clients.
func (*Auth) Login ¶
func (a *Auth) Login(w http.ResponseWriter, r *http.Request)
Login starts GitHub sign-in: it sets a short-lived state cookie and redirects to GitHub. A next query parameter holding a path on this server is where Callback sends the user after signing in; anything else is ignored.
func (*Auth) Logout ¶
func (a *Auth) Logout(w http.ResponseWriter, r *http.Request)
Logout ends the current session.
func (*Auth) ProtectedResourceMetadata ¶
func (a *Auth) ProtectedResourceMetadata(w http.ResponseWriter, r *http.Request)
ProtectedResourceMetadata serves the RFC 9728 metadata of the MCP endpoint.
func (*Auth) Register ¶
func (a *Auth) Register(w http.ResponseWriter, r *http.Request)
Register implements RFC 7591 dynamic client registration for public clients.
func (*Auth) Require ¶
Require returns a handler that serves next only for requests with a valid session, making the user available through UserFrom. Other requests get a 401 JSON error.
func (*Auth) RequireBearer ¶
RequireBearer returns middleware that serves next only for requests with an OAuth access token, in the Authorization header, that was issued for resource and belongs to a permitted user. It makes the user available through UserFrom and the token's scopes through ScopesFrom. Cookies are ignored. Other requests get a 401 whose WWW-Authenticate header points clients at the resource's metadata.
func (*Auth) Resource ¶
Resource returns the URL of the resource that bearer tokens are issued for: the MCP endpoint.
func (*Auth) Revoke ¶
func (a *Auth) Revoke(w http.ResponseWriter, r *http.Request)
Revoke implements RFC 7009 token revocation. Revoking an access or refresh token revokes its whole grant.
func (*Auth) RevokeDisallowedSessions ¶
RevokeDisallowedSessions permanently invalidates the sessions and OAuth grants of removed users, and removes expired OAuth codes and tokens. Call it on startup after loading the access policy.
func (*Auth) RevokeGrant ¶
func (a *Auth) RevokeGrant(w http.ResponseWriter, r *http.Request)
RevokeGrant revokes one of the signed-in user's grants and all its tokens.
type Client ¶
Client is an OAuth client registered through dynamic client registration. Every client is public: it has no secret.
type Code ¶
type Code struct {
Hash string
ClientID string
GitHubID int64
RedirectURI string
Challenge string // PKCE S256 code challenge
Scope string
Resource string
ExpiresAt time.Time
}
Code is an authorization code, keyed by the hash of its value.
type Grant ¶
type Grant struct {
ID string
ClientID string
ClientName string
GitHubID int64
RedirectURI string
Scope string // space-separated
Resource string // audience of its access tokens
CreatedAt time.Time
LastUsedAt *time.Time
}
Grant is an approved authorization: a user let a client act for them.
type OAuth ¶
type OAuth interface {
// AuthorizeURL returns the GitHub page that asks the user to sign in.
AuthorizeURL(redirectURL, state string) string
// ExchangeUser trades an authorization code for the signed-in user.
ExchangeUser(ctx context.Context, code, redirectURL string) (github.User, error)
}
OAuth performs the GitHub sign-in handshake. *github.Client implements it.
type OAuthStore ¶
type OAuthStore interface {
// CreateClient stores a client and returns it with its ID and creation
// time.
CreateClient(ctx context.Context, c Client) (Client, error)
// Client returns a registered client, and whether it exists.
Client(ctx context.Context, id string) (Client, bool, error)
// CountClients returns the number of registered clients.
CountClients(ctx context.Context) (int, error)
// CreateCode stores an authorization code.
CreateCode(ctx context.Context, c Code) error
// RedeemCode spends the unexpired code with the given hash. issue
// validates it and returns the grant and tokens to create; its error is
// returned and the code is spent anyway. It reports false if the code is
// unknown or expired. Redeeming a code twice revokes the grant it
// produced and returns ErrReplayed.
RedeemCode(ctx context.Context, hash string, issue func(Code) (Grant, []Token, error)) (Grant, bool, error)
// RotateRefreshToken spends the unexpired refresh token with the given
// hash. issue validates its grant and user and returns the tokens that
// replace it; if it fails, nothing changes. It reports false if the
// token is unknown or expired. Presenting a rotated token revokes its
// grant and returns ErrReplayed.
RotateRefreshToken(ctx context.Context, hash string, issue func(Grant, User) ([]Token, error)) (Grant, bool, error)
// AccessToken returns the grant and user of an unexpired access token,
// and whether it exists.
AccessToken(ctx context.Context, hash string) (Grant, User, bool, error)
// TouchGrant records that a grant was used at the given time.
TouchGrant(ctx context.Context, id string, at time.Time) error
// Grants returns a user's live grants, newest first.
Grants(ctx context.Context, githubID int64) ([]Grant, error)
// DeleteGrant revokes a user's grant, reporting whether it existed.
DeleteGrant(ctx context.Context, id string, githubID int64) (bool, error)
// RevokeToken revokes the grant of a token, if the token was issued to
// clientID or clientID is empty.
RevokeToken(ctx context.Context, hash, clientID string) error
// DeleteExpiredOAuth removes expired codes and tokens, dead grants, and
// clients created before staleBefore that were never used.
DeleteExpiredOAuth(ctx context.Context, staleBefore time.Time) error
// DeleteDisallowedGrants revokes grants and codes of logins outside
// allowed.
DeleteDisallowedGrants(ctx context.Context, allowed []string) error
}
OAuthStore persists OAuth clients, codes, grants, and tokens.
type Store ¶
type Store interface {
// UpsertUser creates the user or updates its profile.
UpsertUser(ctx context.Context, u User) error
// CreateSession stores a session that expires at the given time.
CreateSession(ctx context.Context, tokenHash string, githubID int64, expires time.Time) error
// SessionUser returns the user of an unexpired session, and whether the
// session exists.
SessionUser(ctx context.Context, tokenHash string) (User, bool, error)
// DeleteSession removes a session.
DeleteSession(ctx context.Context, tokenHash string) error
// DeleteDisallowedSessions removes sessions for logins outside allowed.
DeleteDisallowedSessions(ctx context.Context, allowed []string) error
OAuthStore
}
Store persists users, sessions, and OAuth state. Sessions, codes, and tokens are keyed by the hash of their value, never the value itself.