diff --git a/README.md b/README.md index 61182b3..793d9d9 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ Full reference in [docs/CONFIGURATION.md](docs/CONFIGURATION.md). | `enablePKCE` | `false` | PKCE on the auth code flow. | | `cookieDomain` | auto | Set explicitly for multi-subdomain setups (`.example.com`). | | `cookiePrefix` | `_oidc_raczylo_` | Unique prefix per middleware instance to isolate sessions. | +| `cookiePath` | `/` | Restrict cookies to a path prefix. Set to the middleware's path (e.g. `/app`) to prevent the browser from sending OIDC cookies to unprotected paths, avoiding 431 "Request Header Or Cookie Too Large" errors on mixed-use domains. | | `sessionMaxAge` | `86400` | Session lifetime in seconds. | | `refreshGracePeriodSeconds` | `60` | Proactively refresh tokens this many seconds before expiry. | | `maxRefreshTokenAgeSeconds` | `21600` | Heuristic max stored refresh-token lifetime (6h). Past this, the plugin treats the RT as expired without contacting the IdP — returns 401 to AJAX, full re-auth on navigations. Set `0` to disable. Tune to match your IdP's RT TTL. | diff --git a/main.go b/main.go index 55ba97d..62bf511 100644 --- a/main.go +++ b/main.go @@ -335,6 +335,10 @@ func NewWithContext(ctx context.Context, config *Config, next http.Handler, name // Convert sessionMaxAge from seconds to duration (0 will use default 24 hours) sessionMaxAge := time.Duration(config.SessionMaxAge) * time.Second t.sessionManager, _ = NewSessionManager(config.SessionEncryptionKey, config.ForceHTTPS, config.CookieDomain, config.CookiePrefix, sessionMaxAge, t.logger) // Safe to ignore: session manager creation with fallback to defaults + if config.CookiePath != "" { + t.sessionManager.cookiePath = config.CookiePath + t.logger.Debugf("Using configured cookie path: %s", config.CookiePath) + } t.errorRecoveryManager = NewErrorRecoveryManager(t.logger) // Initialize token resilience manager with default configuration diff --git a/session.go b/session.go index d282d1f..5ba9d74 100644 --- a/session.go +++ b/session.go @@ -382,6 +382,7 @@ type SessionManager struct { cancel context.CancelFunc cookieDomain string cookiePrefix string + cookiePath string sessionMaxAge time.Duration activeSessions int64 poolHits int64 @@ -851,7 +852,12 @@ func (sm *SessionManager) EnhanceSessionSecurity(options *sessions.Options, r *h } options.HttpOnly = true - options.Path = "/" // Ensure cookies are available on all paths for OAuth flow + // Use configured cookie path (default "/" for backward compatibility) + cookiePath := sm.cookiePath + if cookiePath == "" { + cookiePath = "/" + } + options.Path = cookiePath if sm.cookieDomain != "" { options.Domain = sm.cookieDomain diff --git a/settings.go b/settings.go index 4f0d70f..120ffc1 100644 --- a/settings.go +++ b/settings.go @@ -64,23 +64,30 @@ type Config struct { // IdPs do not expose RT TTL on the wire, so this is intentionally a // conservative heuristic; tune to match your provider configuration. // Default 21600 (6h). Set to 0 to disable the check. - MaxRefreshTokenAgeSeconds int `json:"maxRefreshTokenAgeSeconds"` - SessionMaxAge int `json:"sessionMaxAge"` - RateLimit int `json:"rateLimit"` - OverrideScopes bool `json:"overrideScopes"` - DisableReplayDetection bool `json:"disableReplayDetection,omitempty"` - RequireTokenIntrospection bool `json:"requireTokenIntrospection,omitempty"` - AllowOpaqueTokens bool `json:"allowOpaqueTokens,omitempty"` - StrictAudienceValidation bool `json:"strictAudienceValidation,omitempty"` - EnablePKCE bool `json:"enablePKCE"` - ForceHTTPS bool `json:"forceHTTPS"` - AllowPrivateIPAddresses bool `json:"allowPrivateIPAddresses,omitempty"` - MinimalHeaders bool `json:"minimalHeaders,omitempty"` - StripAuthCookies bool `json:"stripAuthCookies,omitempty"` - EnableBackchannelLogout bool `json:"enableBackchannelLogout,omitempty"` - EnableFrontchannelLogout bool `json:"enableFrontchannelLogout,omitempty"` - BackchannelLogoutURL string `json:"backchannelLogoutURL,omitempty"` - FrontchannelLogoutURL string `json:"frontchannelLogoutURL,omitempty"` + MaxRefreshTokenAgeSeconds int `json:"maxRefreshTokenAgeSeconds"` + SessionMaxAge int `json:"sessionMaxAge"` + RateLimit int `json:"rateLimit"` + OverrideScopes bool `json:"overrideScopes"` + DisableReplayDetection bool `json:"disableReplayDetection,omitempty"` + RequireTokenIntrospection bool `json:"requireTokenIntrospection,omitempty"` + AllowOpaqueTokens bool `json:"allowOpaqueTokens,omitempty"` + StrictAudienceValidation bool `json:"strictAudienceValidation,omitempty"` + EnablePKCE bool `json:"enablePKCE"` + ForceHTTPS bool `json:"forceHTTPS"` + AllowPrivateIPAddresses bool `json:"allowPrivateIPAddresses,omitempty"` + MinimalHeaders bool `json:"minimalHeaders,omitempty"` + StripAuthCookies bool `json:"stripAuthCookies,omitempty"` + // CookiePath restricts session cookies to a specific path prefix instead of "/". + // When traefikoidc protects some but not all paths on a domain, set this to the + // middleware's path prefix (e.g. "/app-protegido") so the browser does not send + // the OIDC session cookies to unprotected paths — preventing "Request Header + // Or Cookie Too Large" (431) errors on those paths. + // Default "/" (all paths, current behaviour). + CookiePath string `json:"cookiePath,omitempty"` + EnableBackchannelLogout bool `json:"enableBackchannelLogout,omitempty"` + EnableFrontchannelLogout bool `json:"enableFrontchannelLogout,omitempty"` + BackchannelLogoutURL string `json:"backchannelLogoutURL,omitempty"` + FrontchannelLogoutURL string `json:"frontchannelLogoutURL,omitempty"` // CACertPath is an optional filesystem path to a PEM-encoded CA bundle used // to verify the OIDC provider's TLS certificate. Use this when the provider // is signed by an internal/private CA that is not in the system trust store.