Issue #53: Fix CSRF token handling in reverse proxy

1. ✅ HTTPS Detection Fixed (session.go:723)
- Now uses X-Forwarded-Proto header instead of r.URL.Scheme
- Properly detects HTTPS in reverse proxy environments
2. ✅ SameSite Cookie Attribute Fixed
- Removed automatic SameSiteStrictMode for HTTPS (would break OAuth)
- Keeps SameSiteLaxMode to allow OAuth callbacks from external domains
- Only uses Strict for AJAX requests which don't involve OAuth redirects
3. ✅ Cookie Domain Handling Fixed
- Now respects X-Forwarded-Host header for cookie domain
- Ensures cookies are set for the public domain, not internal proxy domain
4. ✅ EnhanceSessionSecurity Properly Integrated
- Function is now actually called during session save
- Applies security enhancements without breaking OAuth flow

Why Issue #53 Failed Before:

1. Cookies were not marked Secure in HTTPS environments (browser wouldn't send them back)
2. If they had been Secure with SameSite=Strict, Azure callbacks would still fail
3. Cookie domain might have been wrong (internal vs public domain)

Why It Works Now:

1. Cookies are properly marked Secure for HTTPS
2. Uses SameSite=Lax to allow OAuth provider callbacks
3. Cookie domain uses public domain from X-Forwarded-Host
4. CSRF token persists through the entire OAuth flow
This commit is contained in:
2025-08-14 12:10:51 +01:00
parent a4b2bfd70f
commit 1fd762848a
5 changed files with 1293 additions and 6 deletions
+22 -6
View File
@@ -497,13 +497,14 @@ func (sm *SessionManager) EnhanceSessionSecurity(options *sessions.Options, r *h
// Apply stricter security settings for production
if r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil || sm.forceHTTPS {
options.Secure = true
// Enable strict same-site policy for secure connections
options.SameSite = http.SameSiteStrictMode
// IMPORTANT: Keep SameSite as Lax for OAuth flows to work
// Strict mode would block cookies from OAuth provider callbacks
// Only use Strict for AJAX requests which don't involve redirects
}
// Additional security headers analysis
if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
// AJAX requests get stricter same-site policy
// AJAX requests get stricter same-site policy since they don't involve OAuth redirects
options.SameSite = http.SameSiteStrictMode
}
}
@@ -513,13 +514,24 @@ func (sm *SessionManager) EnhanceSessionSecurity(options *sessions.Options, r *h
// Set secure Domain attribute for production
if options.Domain == "" && r != nil {
// Extract domain from Host header for proper cookie scoping
// In reverse proxy setups, use the original host if available
// This ensures cookies work correctly with the public domain
host := r.Host
// Check for X-Forwarded-Host which indicates reverse proxy
if forwardedHost := r.Header.Get("X-Forwarded-Host"); forwardedHost != "" {
// Use the forwarded host for cookie domain
host = forwardedHost
}
if host != "" && !strings.Contains(host, "localhost") && !strings.Contains(host, "127.0.0.1") {
// Only set domain for non-local development
if colonIndex := strings.Index(host, ":"); colonIndex != -1 {
host = host[:colonIndex] // Remove port
}
// For cookie domain, we might want to use the base domain
// to allow cookies to work across subdomains
// But for now, use the exact host for security
options.Domain = host
}
}
@@ -719,10 +731,14 @@ func (sd *SessionData) MarkDirty() {
// Returns:
// - An error if saving any of the session components fails.
func (sd *SessionData) Save(r *http.Request, w http.ResponseWriter) error {
isSecure := strings.HasPrefix(r.URL.Scheme, "https") || sd.manager.forceHTTPS
// Properly detect HTTPS in reverse proxy environments
isSecure := r.Header.Get("X-Forwarded-Proto") == "https" || r.TLS != nil || sd.manager.forceHTTPS
// Set options for all sessions.
// Get base options and enhance with security settings
options := sd.manager.getSessionOptions(isSecure)
options = sd.manager.EnhanceSessionSecurity(options, r)
// Apply enhanced options to all sessions
sd.mainSession.Options = options
sd.accessSession.Options = options
sd.refreshSession.Options = options