Documentation
¶
Overview ¶
Package bff implements the Backend for Frontend (BFF) pattern for secure session management. The BFF holds tokens server-side and uses HTTP-only cookies to identify browser sessions.
Index ¶
- Constants
- Variables
- func APIProxyMiddleware(targetURL string) (func(http.Handler) http.Handler, error)
- func AutoRefreshMiddleware(config RefreshConfig) (func(http.Handler) http.Handler, error)
- func GenerateSessionID() (string, error)
- func OptionalSessionMiddleware(store Store, cookieManager *CookieManager) func(http.Handler) http.Handler
- func OriginMiddleware(allowedOrigins ...string) func(http.Handler) http.Handler
- func RefreshHandler(config RefreshConfig) http.Handler
- func RequireSessionMiddleware(store Store, cookieManager *CookieManager) func(http.Handler) http.Handler
- func SessionMiddleware(config MiddlewareConfig) func(http.Handler) http.Handler
- func SimpleProxy(targetURL string) (http.Handler, error)
- type CookieConfig
- type CookieManager
- type MemoryStore
- func (s *MemoryStore) Cleanup(ctx context.Context) (int, error)
- func (s *MemoryStore) Close() error
- func (s *MemoryStore) Count() int
- func (s *MemoryStore) Create(ctx context.Context, session *Session) error
- func (s *MemoryStore) Delete(ctx context.Context, id string) error
- func (s *MemoryStore) DeleteByUserID(ctx context.Context, userID string) (int, error)
- func (s *MemoryStore) Get(ctx context.Context, id string) (*Session, error)
- func (s *MemoryStore) Touch(ctx context.Context, id string) error
- func (s *MemoryStore) Update(ctx context.Context, session *Session) error
- type MiddlewareConfig
- type OriginConfig
- type OriginValidator
- type Proxy
- type ProxyConfig
- type RefreshConfig
- type Refresher
- type Session
- func (s *Session) GetDPoPKeyPair() (*dpop.KeyPair, error)
- func (s *Session) HasDPoP() bool
- func (s *Session) IsAccessTokenExpired() bool
- func (s *Session) IsExpired() bool
- func (s *Session) IsRefreshTokenExpired() bool
- func (s *Session) NeedsRefresh(threshold time.Duration) bool
- func (s *Session) SetDPoPKeyPair(kp *dpop.KeyPair) error
- type Store
- type StoreConfig
- type TokenErrorResponse
- type TokenResponse
Constants ¶
const (
// ContextKeySession is the context key for the session.
ContextKeySession contextKey = "bff_session"
)
const SessionIDLength = 32
SessionIDLength is the length of generated session IDs in bytes.
Variables ¶
var ( // ErrSessionNotFound is returned when a session is not found. ErrSessionNotFound = errors.New("session not found") // ErrSessionExpired is returned when a session has expired. ErrSessionExpired = errors.New("session expired") // ErrInvalidSession is returned when a session is invalid. ErrInvalidSession = errors.New("invalid session") )
var ErrNoDPoPKeyPair = errors.New("session has no DPoP key pair")
ErrNoDPoPKeyPair is returned when the session has no DPoP key pair.
var ErrRefreshFailed = errors.New("token refresh failed")
ErrRefreshFailed is returned when token refresh fails.
var ErrRefreshTokenExpired = errors.New("refresh token expired")
ErrRefreshTokenExpired is returned when the refresh token has expired.
var ErrTokenEndpointRequired = errors.New("token endpoint required")
ErrTokenEndpointRequired is returned when the token endpoint is not configured.
Functions ¶
func APIProxyMiddleware ¶
APIProxyMiddleware creates middleware that proxies all requests to an API backend. This is useful for wrapping the proxy handler with session middleware.
func AutoRefreshMiddleware ¶
AutoRefreshMiddleware creates middleware that automatically refreshes tokens before expiry.
func GenerateSessionID ¶
GenerateSessionID generates a cryptographically secure session ID.
func OptionalSessionMiddleware ¶
func OptionalSessionMiddleware(store Store, cookieManager *CookieManager) func(http.Handler) http.Handler
OptionalSessionMiddleware creates middleware that loads sessions if present. Requests without sessions are allowed through.
func OriginMiddleware ¶
OriginMiddleware creates origin validation middleware with the given allowed origins. This is a convenience function for simple use cases.
func RefreshHandler ¶
func RefreshHandler(config RefreshConfig) http.Handler
RefreshHandler returns an HTTP handler for explicit token refresh requests. This can be used by the frontend to proactively refresh tokens.
func RequireSessionMiddleware ¶
func RequireSessionMiddleware(store Store, cookieManager *CookieManager) func(http.Handler) http.Handler
RequireSessionMiddleware creates middleware that requires a valid session. This is a convenience function that wraps SessionMiddleware.
func SessionMiddleware ¶
func SessionMiddleware(config MiddlewareConfig) func(http.Handler) http.Handler
SessionMiddleware creates middleware that loads sessions from cookies.
Types ¶
type CookieConfig ¶
type CookieConfig struct {
// Name is the cookie name. Default: "cf_session".
Name string
// Domain is the cookie domain. If empty, uses the request host.
Domain string
// Path is the cookie path. Default: "/".
Path string
// MaxAge is the cookie max age in seconds. Default: 0 (session cookie).
// Set to -1 to delete the cookie.
MaxAge int
// Secure indicates the cookie should only be sent over HTTPS.
// Default: true.
Secure bool
// HTTPOnly prevents JavaScript access to the cookie.
// Default: true (required for security).
HTTPOnly bool
// SameSite controls cross-site cookie behavior.
// Default: SameSiteStrictMode.
SameSite http.SameSite
}
CookieConfig contains configuration for session cookies.
func DefaultCookieConfig ¶
func DefaultCookieConfig() CookieConfig
DefaultCookieConfig returns secure default cookie configuration.
type CookieManager ¶
type CookieManager struct {
// contains filtered or unexported fields
}
CookieManager handles session cookie operations.
func NewCookieManager ¶
func NewCookieManager(config CookieConfig) *CookieManager
NewCookieManager creates a new cookie manager with the given configuration.
func (*CookieManager) ClearSessionCookie ¶
func (m *CookieManager) ClearSessionCookie(w http.ResponseWriter)
ClearSessionCookie removes the session cookie from the response.
func (*CookieManager) Config ¶
func (m *CookieManager) Config() CookieConfig
Config returns the cookie configuration.
func (*CookieManager) GetSessionID ¶
func (m *CookieManager) GetSessionID(r *http.Request) string
GetSessionID extracts the session ID from the request cookie. Returns empty string if the cookie is not present.
func (*CookieManager) SetSessionCookie ¶
func (m *CookieManager) SetSessionCookie(w http.ResponseWriter, sessionID string, expiry time.Time)
SetSessionCookie creates and sets a session cookie on the response.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-memory session store for development and testing. It is thread-safe and supports automatic cleanup of expired sessions.
func NewMemoryStore ¶
func NewMemoryStore(config StoreConfig) *MemoryStore
NewMemoryStore creates a new in-memory session store.
func (*MemoryStore) Cleanup ¶
func (s *MemoryStore) Cleanup(ctx context.Context) (int, error)
Cleanup removes expired sessions.
func (*MemoryStore) Close ¶
func (s *MemoryStore) Close() error
Close stops the cleanup goroutine and releases resources.
func (*MemoryStore) Count ¶
func (s *MemoryStore) Count() int
Count returns the current number of sessions (for testing).
func (*MemoryStore) Create ¶
func (s *MemoryStore) Create(ctx context.Context, session *Session) error
Create stores a new session.
func (*MemoryStore) Delete ¶
func (s *MemoryStore) Delete(ctx context.Context, id string) error
Delete removes a session by ID.
func (*MemoryStore) DeleteByUserID ¶
DeleteByUserID removes all sessions for a user.
type MiddlewareConfig ¶
type MiddlewareConfig struct {
// Store is the session store.
Store Store
// CookieManager handles session cookies.
CookieManager *CookieManager
// RefreshThreshold is how long before access token expiry to trigger refresh.
// Default: 5 minutes.
RefreshThreshold time.Duration
// OnSessionLoad is called after a session is loaded.
// Can be used for logging or session modification.
OnSessionLoad func(ctx context.Context, session *Session) error
// OnSessionExpired is called when a session is expired.
OnSessionExpired func(w http.ResponseWriter, r *http.Request)
// OnSessionInvalid is called when a session is invalid.
OnSessionInvalid func(w http.ResponseWriter, r *http.Request)
// OnNoSession is called when no session is found and RequireSession is true.
OnNoSession func(w http.ResponseWriter, r *http.Request)
// RequireSession when true rejects requests without valid sessions.
RequireSession bool
// TouchOnAccess when true updates LastAccessedAt on each request.
TouchOnAccess bool
}
MiddlewareConfig contains configuration for the BFF middleware.
type OriginConfig ¶
type OriginConfig struct {
// AllowedOrigins is a list of allowed origins.
// Origins should be in the format "https://example.com" (no trailing slash).
AllowedOrigins []string
// AllowedHosts is a list of allowed hosts (without scheme).
// This is an alternative to AllowedOrigins for simpler configuration.
AllowedHosts []string
// OnError is called when origin validation fails.
// If nil, returns 403 Forbidden.
OnError func(w http.ResponseWriter, r *http.Request)
// AllowMissingOrigin allows requests without Origin header.
// Default: false (more secure).
AllowMissingOrigin bool
// CheckReferer uses Referer header as fallback when Origin is missing.
// Default: true.
CheckReferer bool
// SkipMethods is a list of HTTP methods to skip origin validation.
// Typically safe methods (GET, HEAD, OPTIONS) can be skipped.
// Default: none (validate all methods).
SkipMethods []string
}
OriginConfig contains configuration for origin validation.
func DefaultOriginConfig ¶
func DefaultOriginConfig() OriginConfig
DefaultOriginConfig returns default origin validation configuration.
type OriginValidator ¶
type OriginValidator struct {
// contains filtered or unexported fields
}
OriginValidator validates request origins.
func NewOriginValidator ¶
func NewOriginValidator(config OriginConfig) *OriginValidator
NewOriginValidator creates a new origin validator.
func (*OriginValidator) Middleware ¶
func (v *OriginValidator) Middleware() func(http.Handler) http.Handler
Middleware returns HTTP middleware that validates request origins.
func (*OriginValidator) ValidateRequest ¶
func (v *OriginValidator) ValidateRequest(r *http.Request) bool
ValidateRequest validates the request origin.
type Proxy ¶
type Proxy struct {
// contains filtered or unexported fields
}
Proxy proxies requests to an API backend with session-based authentication.
type ProxyConfig ¶
type ProxyConfig struct {
// TargetURL is the base URL of the API backend.
TargetURL string
// UseDPoP enables DPoP proof injection.
// Default: true
UseDPoP bool
// Client is the HTTP client to use for proxied requests.
// If nil, uses http.DefaultClient.
Client *http.Client
// Timeout is the request timeout.
// Default: 30 seconds.
Timeout time.Duration
// OnError is called when a proxy error occurs.
// If nil, returns 502 Bad Gateway.
OnError func(w http.ResponseWriter, r *http.Request, err error)
// OnRequestRewrite allows modifying the proxied request before sending.
OnRequestRewrite func(r *http.Request, session *Session)
// PathRewrite allows modifying the request path.
// The function receives the original path and returns the rewritten path.
PathRewrite func(path string) string
// StripPrefix removes a prefix from the request path before proxying.
StripPrefix string
// HeadersToForward specifies which request headers to forward.
// If empty, forwards all headers except hop-by-hop headers.
HeadersToForward []string
// HeadersToRemove specifies headers to remove from the proxied request.
HeadersToRemove []string
// ResponseHeadersToRemove specifies headers to remove from the response.
ResponseHeadersToRemove []string
}
ProxyConfig contains configuration for the API proxy.
func DefaultProxyConfig ¶
func DefaultProxyConfig() ProxyConfig
DefaultProxyConfig returns default proxy configuration.
type RefreshConfig ¶
type RefreshConfig struct {
// Store is the session store.
Store Store
// CookieManager handles session cookies.
CookieManager *CookieManager
// TokenEndpoint is the OAuth token endpoint URL.
TokenEndpoint string
// ClientID is the OAuth client ID.
ClientID string
// ClientSecret is the OAuth client secret (optional for public clients).
ClientSecret string //nolint:gosec // G117: config field, not a hardcoded secret
// UseDPoP enables DPoP for token refresh requests.
// When true, generates a new DPoP key pair and binds the new tokens to it.
// Default: true.
UseDPoP bool
// Client is the HTTP client to use for refresh requests.
// If nil, uses http.DefaultClient.
Client *http.Client
// Timeout is the refresh request timeout.
// Default: 30 seconds.
Timeout time.Duration
// RefreshThreshold is how early before expiry to refresh tokens.
// Default: 5 minutes.
RefreshThreshold time.Duration
// OnRefreshSuccess is called when tokens are successfully refreshed.
OnRefreshSuccess func(ctx context.Context, session *Session)
// OnRefreshError is called when token refresh fails.
OnRefreshError func(w http.ResponseWriter, r *http.Request, err error)
// ParseTokenResponse allows custom parsing of the token response.
// If nil, uses the default OAuth2 token response format.
ParseTokenResponse func(body []byte) (*TokenResponse, error)
}
RefreshConfig contains configuration for the token refresh handler.
func DefaultRefreshConfig ¶
func DefaultRefreshConfig() RefreshConfig
DefaultRefreshConfig returns default refresh configuration.
type Refresher ¶
type Refresher struct {
// contains filtered or unexported fields
}
Refresher handles automatic token refresh.
func NewRefresher ¶
func NewRefresher(config RefreshConfig) (*Refresher, error)
NewRefresher creates a new token refresher.
func (*Refresher) Middleware ¶
Middleware returns HTTP middleware that automatically refreshes expired access tokens.
type Session ¶
type Session struct {
// ID is the unique session identifier.
ID string `json:"id"`
// UserID is the authenticated user's ID.
UserID uuid.UUID `json:"user_id"`
// OrganizationID is the current organization context (optional).
OrganizationID *uuid.UUID `json:"organization_id,omitempty"`
// AccessToken is the OAuth access token.
AccessToken string `json:"access_token"`
// RefreshToken is the OAuth refresh token.
RefreshToken string `json:"refresh_token"`
// AccessTokenExpiresAt is when the access token expires.
AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`
// RefreshTokenExpiresAt is when the refresh token expires.
RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"`
// DPoPKeyPairJSON is the serialized DPoP key pair for this session.
// The BFF uses this to sign DPoP proofs when proxying requests to the API.
DPoPKeyPairJSON []byte `json:"dpop_key_pair,omitempty"`
// DPoPThumbprint is the JWK thumbprint of the DPoP key pair.
DPoPThumbprint string `json:"dpop_thumbprint,omitempty"`
// Metadata contains optional session metadata.
Metadata map[string]string `json:"metadata,omitempty"`
// CreatedAt is when the session was created.
CreatedAt time.Time `json:"created_at"`
// UpdatedAt is when the session was last updated.
UpdatedAt time.Time `json:"updated_at"`
// LastAccessedAt is when the session was last accessed.
LastAccessedAt time.Time `json:"last_accessed_at"`
// ExpiresAt is when the session expires (based on refresh token or absolute timeout).
ExpiresAt time.Time `json:"expires_at"`
// IPAddress is the IP address that created the session.
IPAddress string `json:"ip_address,omitempty"`
// UserAgent is the user agent that created the session.
UserAgent string `json:"user_agent,omitempty"`
}
Session represents a server-side session with stored tokens and DPoP keys.
func GetSession ¶
GetSession retrieves the session from the request context.
func NewSession ¶
func NewSession(userID uuid.UUID, accessToken, refreshToken string, accessExpiry, refreshExpiry time.Duration) (*Session, error)
NewSession creates a new session with the given parameters.
func (*Session) GetDPoPKeyPair ¶
GetDPoPKeyPair deserializes and returns the session's DPoP key pair.
func (*Session) IsAccessTokenExpired ¶
IsAccessTokenExpired returns true if the access token has expired.
func (*Session) IsRefreshTokenExpired ¶
IsRefreshTokenExpired returns true if the refresh token has expired.
func (*Session) NeedsRefresh ¶
NeedsRefresh returns true if the access token needs to be refreshed. This returns true if the access token is expired or will expire soon.
type Store ¶
type Store interface {
// Create stores a new session and returns the session ID.
Create(ctx context.Context, session *Session) error
// Get retrieves a session by ID.
// Returns ErrSessionNotFound if the session doesn't exist.
// Returns ErrSessionExpired if the session has expired.
Get(ctx context.Context, id string) (*Session, error)
// Update updates an existing session.
// Returns ErrSessionNotFound if the session doesn't exist.
Update(ctx context.Context, session *Session) error
// Delete removes a session by ID.
// Returns ErrSessionNotFound if the session doesn't exist.
Delete(ctx context.Context, id string) error
// DeleteByUserID removes all sessions for a user.
// Returns the number of sessions deleted.
DeleteByUserID(ctx context.Context, userID string) (int, error)
// Touch updates the LastAccessedAt timestamp.
// This is used to track session activity without modifying other fields.
Touch(ctx context.Context, id string) error
// Cleanup removes expired sessions.
// Returns the number of sessions removed.
Cleanup(ctx context.Context) (int, error)
// Close closes the store and releases any resources.
Close() error
}
Store defines the interface for session storage. Implementations must be safe for concurrent use.
type StoreConfig ¶
type StoreConfig struct {
// CleanupInterval is how often to run automatic cleanup.
// Set to 0 to disable automatic cleanup.
CleanupInterval int
// MaxSessions is the maximum number of sessions to store (0 = unlimited).
MaxSessions int
// EncryptionKey is used to encrypt sensitive session data.
// If nil, session data is stored unencrypted.
EncryptionKey []byte
}
StoreConfig contains common configuration for session stores.
func DefaultStoreConfig ¶
func DefaultStoreConfig() StoreConfig
DefaultStoreConfig returns sensible default configuration.
type TokenErrorResponse ¶
type TokenErrorResponse struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
TokenErrorResponse represents an OAuth2 error response.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
Scope string `json:"scope,omitempty"`
}
TokenResponse represents the OAuth2 token response.