Documentation
¶
Overview ¶
Package forgeoauth implements an OAuth 2.1 authorization server for remote MCP servers. It supports the authorization code flow with mandatory PKCE (S256), stateless client validation via Client ID Metadata Documents (CIMD), and optional refresh tokens via the offline_access scope.
Standards ¶
- OAuth 2.1 (draft-15): PKCE mandatory, no implicit flow, no ROPC
- RFC 8414: Authorization Server Metadata
- CIMD: stateless client validation by fetching the client_id URL
Quick start ¶
store, err := forgeoauth.NewSQLiteStore("./oauth.db")
if err != nil {
log.Fatal(err)
}
srv := forgeoauth.New(forgeoauth.Config{
Issuer: "https://cms.example.com",
VerifyBearer: func(token string) bool {
_, ok := forge.VerifyTokenString(token, app.Secret(), app.TokenStore())
return ok
},
}, store)
// srv.Handler() mounts all OAuth endpoints.
// Embed in a larger mux via forgemcp.WithOAuth(srv).
Index ¶
- Variables
- func VerifyPKCE(codeVerifier, codeChallenge string) bool
- type AccessToken
- type AuthCode
- type CIMDDoc
- type Config
- type RefreshToken
- type SQLiteStore
- func (s *SQLiteStore) Close() error
- func (s *SQLiteStore) DeleteCode(ctx context.Context, code string) error
- func (s *SQLiteStore) DeleteRefreshToken(ctx context.Context, token string) error
- func (s *SQLiteStore) GetCode(ctx context.Context, code string) (AuthCode, error)
- func (s *SQLiteStore) GetRefreshToken(ctx context.Context, token string) (RefreshToken, error)
- func (s *SQLiteStore) GetToken(ctx context.Context, token string) (AccessToken, error)
- func (s *SQLiteStore) SaveCode(ctx context.Context, c AuthCode) error
- func (s *SQLiteStore) SaveRefreshToken(ctx context.Context, t RefreshToken) error
- func (s *SQLiteStore) SaveToken(ctx context.Context, t AccessToken) error
- type Server
- type Store
Constants ¶
This section is empty.
Variables ¶
var ( // ErrTokenNotFound is returned when the access token does not exist in the store. ErrTokenNotFound = errors.New("forgeoauth: access token not found") // ErrTokenExpired is returned when the access token exists but has passed its ExpiresAt. ErrTokenExpired = errors.New("forgeoauth: access token expired") // ErrCodeNotFound is returned by [Store.GetCode] when the code does not exist. ErrCodeNotFound = errors.New("forgeoauth: authorization code not found") // ErrRefreshTokenNotFound is returned by [Store.GetRefreshToken] when the token does not exist. ErrRefreshTokenNotFound = errors.New("forgeoauth: refresh token not found") )
Sentinel errors returned by Server.ValidateAccessToken.
Functions ¶
func VerifyPKCE ¶
VerifyPKCE checks that BASE64URL(SHA256(codeVerifier)) equals codeChallenge. Uses constant-time comparison to prevent timing attacks. Returns true when the verifier is valid (S256 method only).
Types ¶
type AccessToken ¶
type AccessToken struct {
// Token is the raw token value.
Token string
// ClientID is the HTTPS URL identifying the OAuth client.
ClientID string
// Scope is the space-separated scope string for this token.
Scope string
// ExpiresAt is the UTC time after which this token is invalid.
ExpiresAt time.Time
}
AccessToken is a Bearer access token issued after code exchange.
type AuthCode ¶
type AuthCode struct {
// Code is the raw authorization code value (random hex, 32 bytes).
Code string
// ClientID is the HTTPS URL identifying the OAuth client (CIMD).
ClientID string
// RedirectURI is the callback URL for this authorization request.
RedirectURI string
// Scope is the space-separated scope string requested by the client.
Scope string
// CodeChallenge is BASE64URL(SHA256(code_verifier)) (S256 method).
CodeChallenge string
// ExpiresAt is the UTC time after which this code must be rejected.
ExpiresAt time.Time
}
AuthCode is a short-lived, single-use PKCE authorization code.
type CIMDDoc ¶
type CIMDDoc struct {
ClientID string `json:"client_id"`
ClientName string `json:"client_name"`
RedirectURIs []string `json:"redirect_uris"`
}
CIMDDoc is the parsed client identity metadata document (CIMD). The server fetches this document from the client_id URL on every authorization request — no client registration database required.
type Config ¶
type Config struct {
// Issuer is the HTTPS base URL of this authorization server.
// Included in RFC 8414 metadata and in WWW-Authenticate headers.
// Example: "https://cms.example.com"
// Required — New panics if empty.
Issuer string
// AccessTokenTTL is how long access tokens remain valid.
// Default: 1 hour.
AccessTokenTTL time.Duration
// AuthCodeTTL is how long authorization codes remain valid.
// Default: 5 minutes.
AuthCodeTTL time.Duration
// VerifyBearer validates a Forge bearer token submitted at /oauth/authorize.
// Returns true if the token authenticates a valid user on the Forge site.
// Required — New panics if nil.
//
// Example using forge.VerifyTokenString (forge-cms.dev/forge v1.25.0+):
//
// VerifyBearer: func(token string) bool {
// _, ok := forge.VerifyTokenString(token, app.Secret(), app.TokenStore())
// return ok
// },
VerifyBearer func(token string) bool
// HTTPClient is used for CIMD metadata fetches.
// Default: &http.Client{Timeout: 5 * time.Second}.
HTTPClient *http.Client
}
Config holds the configuration for the OAuth 2.1 authorization server.
type RefreshToken ¶
type RefreshToken struct {
// Token is the raw token value.
Token string
// ClientID is the HTTPS URL identifying the OAuth client.
ClientID string
// Scope is the space-separated scope string for this token.
Scope string
}
RefreshToken is a long-lived token used to obtain new access tokens. In v1, refresh tokens do not expire. They are issued only when the authorization request includes the [offline_access] scope.
type SQLiteStore ¶
type SQLiteStore struct {
// contains filtered or unexported fields
}
SQLiteStore is a SQLite-backed implementation of Store. It is safe for concurrent use. The three OAuth tables are created automatically by NewSQLiteStore if they do not already exist.
func NewSQLiteStore ¶
func NewSQLiteStore(path string) (*SQLiteStore, error)
NewSQLiteStore opens (or creates) a SQLite database at path and initialises the three OAuth tables. Use path ":memory:" for in-process testing.
func (*SQLiteStore) Close ¶
func (s *SQLiteStore) Close() error
Close closes the underlying database connection.
func (*SQLiteStore) DeleteCode ¶
func (s *SQLiteStore) DeleteCode(ctx context.Context, code string) error
func (*SQLiteStore) DeleteRefreshToken ¶
func (s *SQLiteStore) DeleteRefreshToken(ctx context.Context, token string) error
func (*SQLiteStore) GetRefreshToken ¶
func (s *SQLiteStore) GetRefreshToken(ctx context.Context, token string) (RefreshToken, error)
func (*SQLiteStore) GetToken ¶
func (s *SQLiteStore) GetToken(ctx context.Context, token string) (AccessToken, error)
func (*SQLiteStore) SaveCode ¶
func (s *SQLiteStore) SaveCode(ctx context.Context, c AuthCode) error
func (*SQLiteStore) SaveRefreshToken ¶
func (s *SQLiteStore) SaveRefreshToken(ctx context.Context, t RefreshToken) error
func (*SQLiteStore) SaveToken ¶
func (s *SQLiteStore) SaveToken(ctx context.Context, t AccessToken) error
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is an OAuth 2.1 authorization server. Create with New; use [Handler] to mount endpoints.
func New ¶
New creates a Server with the given configuration and store. Panics if cfg.Issuer is empty or cfg.VerifyBearer is nil.
func (*Server) Handler ¶
Handler returns an http.Handler that serves all OAuth 2.1 endpoints:
GET /.well-known/oauth-authorization-server — RFC 8414 metadata GET /oauth/authorize — authorization form POST /oauth/authorize — form submission POST /oauth/token — code exchange and token refresh
func (*Server) Issuer ¶
Issuer returns the server's configured issuer URL. Used by forge-mcp to populate the authorization_servers field in /.well-known/oauth-protected-resource (RFC 9728).
func (*Server) ValidateAccessToken ¶
ValidateAccessToken looks up a Bearer access token in the store. Returns the AccessToken record on success. Returns ErrTokenNotFound if the token is unknown, or ErrTokenExpired if the token exists but has passed its ExpiresAt time.
type Store ¶
type Store interface {
// SaveCode persists an authorization code.
SaveCode(ctx context.Context, c AuthCode) error
// GetCode retrieves an authorization code by its code value.
// Returns [ErrCodeNotFound] if the code does not exist.
GetCode(ctx context.Context, code string) (AuthCode, error)
// DeleteCode removes an authorization code (e.g. after single use).
DeleteCode(ctx context.Context, code string) error
// SaveToken persists an access token.
SaveToken(ctx context.Context, t AccessToken) error
// GetToken retrieves an access token by its token value.
// Returns [ErrTokenNotFound] if the token does not exist.
GetToken(ctx context.Context, token string) (AccessToken, error)
// SaveRefreshToken persists a refresh token.
SaveRefreshToken(ctx context.Context, t RefreshToken) error
// GetRefreshToken retrieves a refresh token by its token value.
// Returns [ErrRefreshTokenNotFound] if the token does not exist.
GetRefreshToken(ctx context.Context, token string) (RefreshToken, error)
// DeleteRefreshToken removes a refresh token.
DeleteRefreshToken(ctx context.Context, token string) error
}
Store provides persistence for OAuth 2.1 state: authorization codes, access tokens, and refresh tokens. All implementations must be safe for concurrent use. SQLiteStore is the bundled production implementation.