Clone
1
cookies
Haelnorr edited this page 2026-01-21 19:20:52 +11:00

cookies v1.0.0

HTTP cookie utilities for Go web applications with security best practices.

Installation

go get git.haelnorr.com/h/golib/cookies

Key Concepts and Features

The cookies package provides secure utilities for handling HTTP cookies in Go web applications. All cookies are set with the HttpOnly flag by default to prevent XSS attacks.

Core Functions

  • SetCookie: Set secure cookies with configurable path and max-age
  • DeleteCookie: Properly delete cookies by setting expiration in the past
  • SetPageFrom: Track the referring page for post-login redirects
  • CheckPageFrom: Retrieve and delete pagefrom tracking cookie

Quick Start

package main

import (
    "net/http"
    "git.haelnorr.com/h/golib/cookies"
)

func loginHandler(w http.ResponseWriter, r *http.Request) {
    // Set pagefrom cookie before redirecting to login
    cookies.SetPageFrom(w, r, "example.com")
    http.Redirect(w, r, "/login", http.StatusFound)
}

func postLoginHandler(w http.ResponseWriter, r *http.Request) {
    // Set session cookie after successful login
    cookies.SetCookie(w, "session", "/", "user-session-id", 3600)
    
    // Redirect back to the original page
    redirectTo := cookies.CheckPageFrom(w, r)
    http.Redirect(w, r, redirectTo, http.StatusFound)
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    // Delete session cookie
    cookies.DeleteCookie(w, "session", "/")
    http.Redirect(w, r, "/login", http.StatusFound)
}

Configuration

All cookies set by this package include:

  • HttpOnly: Prevents JavaScript access (XSS protection)
  • Configurable Path: Restricts cookie to specific paths
  • Configurable Max-Age: Controls cookie lifetime

Environment Variables

The package doesn't require specific environment variables, but commonly used ones include:

// ENV COOKIE_DOMAIN: Cookie domain (optional)
// ENV COOKIE_SECURE: Use HTTPS-only cookies (default: true in production)
// ENV TRUSTED_HOST: Host for pagefrom validation (recommended)

Detailed Usage

Setting Cookies

// Basic session cookie (1 hour)
cookies.SetCookie(w, "session", "/", "abc123", 3600)

// Persistent cookie (30 days)
cookies.SetCookie(w, "preferences", "/", "dark-mode", 2592000)

// Temporary cookie (browser session)
cookies.SetCookie(w, "temp", "/", "data", 0)

// Short-lived cookie (5 minutes)
cookies.SetCookie(w, "csrf", "/", "token", 300)

Deleting Cookies

// Delete session cookie
cookies.DeleteCookie(w, "session", "/")

// Delete cookie with specific path
cookies.DeleteCookie(w, "preferences", "/user")

// Delete admin cookie
cookies.DeleteCookie(w, "admin_token", "/admin")

Pagefrom Tracking

The pagefrom functionality helps maintain user experience by remembering where users were before authentication:

// Middleware to track pagefrom before auth
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if needsAuth(r) && !isAuthenticated(r) {
            // Track where user was going
            cookies.SetPageFrom(w, r, "example.com")
            http.Redirect(w, r, "/login", http.StatusFound)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// After successful login
func loginSuccessHandler(w http.ResponseWriter, r *http.Request) {
    // Authenticate user...
    
    // Get original destination
    redirectTo := cookies.CheckPageFrom(w, r)
    
    // Default to dashboard if no pagefrom
    if redirectTo == "/" {
        redirectTo = "/dashboard"
    }
    
    http.Redirect(w, r, redirectTo, http.StatusFound)
}

Host Validation

Pagefrom tracking includes host validation to prevent open redirects:

// Only allows redirects from trusted host
cookies.SetPageFrom(w, r, "example.com")

// This will be rejected and redirect to "/"
// Referer: http://evil.com/steal-data

// This will be accepted
// Referer: http://example.com/dashboard

Integration

With HWS (Web Server)

import "git.haelnorr.com/h/golib/hws"
import "git.haelnorr.com/h/golib/cookies"

func main() {
    server := hws.NewServer()
    
    // Add auth middleware
    server.Use(authMiddleware)
    
    // Login routes
    server.Get("/login", loginHandler)
    server.Post("/login", postLoginHandler)
    server.Get("/logout", logoutHandler)
    
    server.Start()
}

func authMiddleware(ctx *hws.Context) {
    if !isAuthenticated(ctx.Request) {
        cookies.SetPageFrom(ctx.ResponseWriter, ctx.Request, "example.com")
        ctx.Redirect("/login", http.StatusFound)
        return
    }
    ctx.Next()
}

With HWSAuth (Authentication)

import "git.haelnorr.com/h/golib/hwsauth"
import "git.haelnorr.com/h/golib/cookies"

func loginHandler(ctx *hws.Context) {
    // Validate credentials...
    
    if valid {
        // Create JWT token
        token, err := hwsauth.CreateToken(userID)
        if err != nil {
            ctx.Error("Failed to create token", http.StatusInternalServerError)
            return
        }
        
        // Set cookie with token
        cookies.SetCookie(ctx.ResponseWriter, "auth", "/", token, 3600)
        
        // Redirect to original destination
        redirectTo := cookies.CheckPageFrom(ctx.ResponseWriter, ctx.Request)
        ctx.Redirect(redirectTo, http.StatusFound)
    }
}

Best Practices

  1. Use HttpOnly: All cookies are HttpOnly by default for security
  2. Set appropriate paths: Restrict cookies to necessary paths
  3. Use reasonable max-age: Don't keep cookies longer than needed
  4. Validate pagefrom: Always use trusted host validation
  5. Delete properly: Use DeleteCookie instead of setting empty values
// Good: Specific path and reasonable max-age
cookies.SetCookie(w, "session", "/", sessionID, 3600)

// Good: Proper deletion
cookies.DeleteCookie(w, "session", "/")

// Avoid: Too broad or too persistent
cookies.SetCookie(w, "data", "/", "value", 31536000) // 1 year

Security Considerations

  1. HTTPS in Production: Always use HTTPS for cookies with sensitive data
  2. SameSite: Consider SameSite attribute for CSRF protection
  3. Domain Restriction: Set domain to restrict cookies to your domain
  4. Path Restriction: Use specific paths when possible
// Production example with additional security
func setSecureCookie(w http.ResponseWriter, name, value string) {
    cookie := &http.Cookie{
        Name:     name,
        Value:    value,
        Path:     "/",
        Domain:   "example.com",
        MaxAge:   3600,
        HttpOnly: true,
        Secure:   true, // HTTPS only
        SameSite: http.SameSiteStrictMode,
    }
    http.SetCookie(w, cookie)
}

Troubleshooting

Common Issues

  1. Cookie not setting: Check that response hasn't been written yet
  2. Cookie not deleting: Ensure path matches original cookie path
  3. Pagefrom not working: Verify trusted host matches referer host
  4. Redirect loops: Check authentication logic carefully

Debug Tips

// Debug cookie headers
func debugCookies(w http.ResponseWriter, r *http.Request) {
    fmt.Printf("Request cookies: %v\n", r.Cookies())
    
    // Check response headers after setting cookies
    headers := w.Header()
    fmt.Printf("Set-Cookie headers: %v\n", headers["Set-Cookie"])
}

Testing Pagefrom

// Test pagefrom flow manually
func testPagefromFlow() {
    // 1. Set pagefrom
    w := httptest.NewRecorder()
    r := httptest.NewRequest("GET", "/protected", nil)
    r.Header.Set("Referer", "http://example.com/dashboard")
    
    cookies.SetPageFrom(w, r, "example.com")
    
    // 2. Check pagefrom
    w2 := httptest.NewRecorder()
    r2 := httptest.NewRequest("GET", "/login", nil)
    
    // Add the cookie from step 1
    for _, cookie := range w.Result().Cookies() {
        r2.AddCookie(cookie)
    }
    
    redirectTo := cookies.CheckPageFrom(w2, r2)
    fmt.Printf("Redirect to: %s\n", redirectTo) // Should be "/dashboard"
}

See Also

  • HWS - Web server framework for building applications
  • HWSAuth - JWT authentication system
  • env - Environment variable configuration