Documentation
¶
Overview ¶
Package browsersession provides browser-based OIDC authentication and cookie-managed sessions for the portal UI. It implements:
- HMAC-SHA256 signed JWT session cookies (stateless)
- OIDC authorization code flow with PKCE
- Cookie-based authenticator for the HTTP auth chain
Index ¶
- Constants
- Variables
- func ClearCookie(w http.ResponseWriter, cfg *CookieConfig)
- func IsValidSameSite(s string) bool
- func ParseSameSite(s string) http.SameSite
- func SetCookie(w http.ResponseWriter, cfg *CookieConfig, tokenString string)
- func SignSession(claims SessionClaims, cfg *CookieConfig) (string, error)
- type Authenticator
- type CookieConfig
- type Flow
- type FlowConfig
- type SessionClaims
Constants ¶
const ( DefaultCookieName = "mcp_session" DefaultCookiePath = "/" DefaultTTL = 8 * time.Hour )
Default configuration values.
const CSRFHeaderName = "X-CSRF-Token" //nolint:gosec // header name, not a credential
CSRFHeaderName is the request header that carries the CSRF token on state-changing requests authenticated by a session cookie. Because it is a custom header, a cross-origin page cannot set it on a form/navigation submit, and the browser blocks scripted cross-origin reads of the token, so only same-origin callers that legitimately fetched the token can send a valid value.
const DefaultPortalPath = "/portal/"
DefaultPortalPath is the default redirect path for the portal UI.
Variables ¶
var ErrCSRFInvalid = errors.New("missing or invalid CSRF token")
ErrCSRFInvalid is returned when a cookie-authenticated, state-changing request is missing a valid X-CSRF-Token header.
Functions ¶
func ClearCookie ¶
func ClearCookie(w http.ResponseWriter, cfg *CookieConfig)
ClearCookie removes the session cookie.
func IsValidSameSite ¶ added in v1.96.0
IsValidSameSite reports whether s is a recognized same_site config value (case-insensitive): "" (defaults to Lax), "lax", "strict", or "none". Config validation uses this to reject a typo at startup rather than let ParseSameSite silently downgrade an unrecognized value to Lax.
func ParseSameSite ¶ added in v1.96.0
ParseSameSite maps a configuration string ("lax", "strict", "none") to an http.SameSite mode. An empty or unrecognized value yields the zero value, which effectiveSameSite treats as Lax, the safe default. The match is case-insensitive.
func SetCookie ¶
func SetCookie(w http.ResponseWriter, cfg *CookieConfig, tokenString string)
SetCookie writes the session JWT as an HTTP-only cookie.
func SignSession ¶
func SignSession(claims SessionClaims, cfg *CookieConfig) (string, error)
SignSession creates a signed JWT string from session claims.
Types ¶
type Authenticator ¶
type Authenticator struct {
// contains filtered or unexported fields
}
Authenticator checks for a valid session cookie and returns user info. It is designed to be used as the first step in an HTTP auth chain — if no cookie is present or the cookie is invalid, it returns nil (allowing fallback to token-based authentication).
func NewAuthenticator ¶
func NewAuthenticator(cfg CookieConfig) *Authenticator
NewAuthenticator creates a cookie-based authenticator.
func (*Authenticator) AuthenticateHTTP ¶
func (a *Authenticator) AuthenticateHTTP(r *http.Request) (*middleware.UserInfo, error)
AuthenticateHTTP checks the HTTP request for a valid session cookie. Returns nil, nil when no valid cookie is found (no error, just unauthenticated).
func (*Authenticator) ExtractIDToken ¶ added in v0.36.2
func (a *Authenticator) ExtractIDToken(r *http.Request) string
ExtractIDToken extracts the OIDC id_token from the session cookie. Returns empty string if no valid session exists or no id_token is stored.
func (*Authenticator) IssueCSRFToken ¶ added in v1.96.0
func (a *Authenticator) IssueCSRFToken(subject string) string
IssueCSRFToken derives a stateless CSRF token bound to the session subject.
The token is an HMAC-SHA256 of the subject under the same signing key that protects the session cookie, so it needs no server-side storage and is verified by recomputation. It is safe to hand to the browser (e.g. in the /me response) because an attacker on another origin can neither read the same-origin response body (blocked by the browser) nor forge the HMAC without the signing key.
func (*Authenticator) ValidateCSRFRequest ¶ added in v1.96.0
func (a *Authenticator) ValidateCSRFRequest(r *http.Request, subject string) error
ValidateCSRFRequest enforces CSRF protection for a cookie-authenticated request. Safe (read-only) methods always pass. State-changing methods must carry an X-CSRF-Token header whose value matches the token bound to subject; otherwise ErrCSRFInvalid is returned.
Token-authenticated (API-key / Bearer) requests do not reach this check — they are not vulnerable to CSRF because the credential is not attached automatically by the browser.
type CookieConfig ¶
type CookieConfig struct {
// Name is the cookie name (default: "mcp_session").
Name string
// Domain restricts the cookie to a specific domain.
Domain string
// Path restricts the cookie to a specific path (default: "/").
Path string
// Secure marks the cookie as HTTPS-only (default: true).
Secure bool
// SameSite controls cross-site cookie behavior (default: Lax).
SameSite http.SameSite
// TTL is the session lifetime (default: 8h).
TTL time.Duration
// Key is the HMAC-SHA256 signing key. Must be at least 32 bytes.
Key []byte
}
CookieConfig controls session cookie behavior.
func (*CookieConfig) IsCrossSiteCookieMode ¶ added in v1.96.0
func (c *CookieConfig) IsCrossSiteCookieMode() bool
IsCrossSiteCookieMode reports whether the effective SameSite setting permits cross-site transmission of the session cookie (SameSite=None). In that mode the browser's built-in CSRF defense is disabled and the X-CSRF-Token layer becomes the sole protection, so callers should log a startup warning.
type Flow ¶
type Flow struct {
// contains filtered or unexported fields
}
Flow implements the OIDC authorization code flow with PKCE.
func NewFlow ¶
func NewFlow(ctx context.Context, cfg FlowConfig) (*Flow, error)
NewFlow creates a new OIDC flow by performing provider discovery.
func (*Flow) CallbackHandler ¶
func (f *Flow) CallbackHandler(w http.ResponseWriter, r *http.Request)
CallbackHandler processes the OIDC provider's callback after authentication. It validates the state, exchanges the authorization code for tokens, creates a session cookie, and redirects to the portal.
func (*Flow) LoginHandler ¶
func (f *Flow) LoginHandler(w http.ResponseWriter, r *http.Request)
LoginHandler initiates the OIDC authorization code flow. It generates state + PKCE verifier, stores them in a temporary cookie, and redirects the user to the OIDC provider's authorization endpoint.
An optional `return_to` query parameter is captured into the state cookie and used as the post-login redirect on callback. This lets the admin SPA send a 401-recovery flow back to the page the operator was on (e.g., a connection settings page where they clicked Connect) rather than dropping them at the portal root. The value must be a site-relative path; anything else is silently dropped so an attacker can't dangle an open redirect off the login endpoint.
func (*Flow) LogoutHandler ¶
func (f *Flow) LogoutHandler(w http.ResponseWriter, r *http.Request)
LogoutHandler clears the session cookie and redirects to the OIDC end_session endpoint.
type FlowConfig ¶
type FlowConfig struct {
// Issuer is the OIDC provider's issuer URL.
Issuer string
// ClientID is the OIDC client identifier.
ClientID string
// ClientSecret is the OIDC client secret for confidential clients.
ClientSecret string // #nosec G117 -- OIDC client secret from admin config
// RedirectURI is the callback URL (e.g., "https://example.com/portal/auth/callback").
RedirectURI string
// Scopes to request (default: [openid, profile, email]).
Scopes []string
// RoleClaim is the JSON path to roles in the id_token claims.
RoleClaim string
// RolePrefix filters roles to those with this prefix.
RolePrefix string
// Cookie configures the session cookie.
Cookie CookieConfig
// PostLoginRedirect is where to redirect after successful login (default: DefaultPortalPath).
PostLoginRedirect string
// PostLogoutRedirect is the absolute URL sent as post_logout_redirect_uri
// to the OIDC provider. Must be an absolute URL (Keycloak rejects relative paths).
// Falls back to PostLoginRedirect if empty.
PostLogoutRedirect string
// HTTPClient is used for OIDC discovery and token exchange.
// If nil, http.DefaultClient is used.
HTTPClient *http.Client
// OnLogin, if set, is called after a successful login with the person's
// email and name derived from the id_token. It records browser-session
// (portal/admin SPA) users in the known-users directory (#614). It must be
// non-blocking and best-effort — login must never depend on it.
OnLogin func(email, firstName, lastName string)
}
FlowConfig configures the OIDC authorization code flow.
type SessionClaims ¶
type SessionClaims struct {
UserID string `json:"sub"`
Email string `json:"email,omitempty"`
Roles []string `json:"roles"`
IDToken string `json:"idt,omitempty"` // raw id_token for logout id_token_hint
// FirstName and LastName are derived from the id_token at login and used to
// record the person in the known-users directory (#614). They are NOT
// persisted in the session cookie (json:"-"); they exist only in-memory for
// the duration of the callback, so the cookie stays small and these are
// absent on subsequent cookie-authenticated requests.
FirstName string `json:"-"`
LastName string `json:"-"`
}
SessionClaims holds the claims stored in the session JWT cookie.
func ParseFromRequest ¶
func ParseFromRequest(r *http.Request, cfg *CookieConfig) (*SessionClaims, error)
ParseFromRequest reads the session cookie from the request and verifies it.
func VerifySession ¶
func VerifySession(tokenString string, key []byte) (*SessionClaims, error)
VerifySession validates a signed JWT and returns the session claims.