How do I handle HTTP sessions and cookies in Go?

Go provides robust support for HTTP cookies and sessions through the net/http package and third-party libraries like gorilla/sessions. This guide covers both basic cookie management and advanced session handling.

Cookie Management with net/http

Reading Cookies from Requests

Go's http.Request provides methods to access cookies sent by clients:

package main

import (
    "fmt"
    "net/http"
)

func cookieHandler(w http.ResponseWriter, r *http.Request) {
    // Get a specific cookie
    cookie, err := r.Cookie("session_token")
    if err != nil {
        if err == http.ErrNoCookie {
            fmt.Fprintf(w, "No session_token cookie found")
            return
        }
        http.Error(w, "Error reading cookie", http.StatusInternalServerError)
        return
    }

    fmt.Fprintf(w, "Cookie value: %s", cookie.Value)

    // Get all cookies
    cookies := r.Cookies()
    for _, c := range cookies {
        fmt.Printf("Cookie: %s = %s\n", c.Name, c.Value)
    }
}

Setting Cookies in Responses

Create and set cookies with proper attributes for security:

func setCookieHandler(w http.ResponseWriter, r *http.Request) {
    // Basic cookie
    basicCookie := &http.Cookie{
        Name:  "user_preference",
        Value: "dark_mode",
        Path:  "/",
    }
    http.SetCookie(w, basicCookie)

    // Secure session cookie
    sessionCookie := &http.Cookie{
        Name:     "session_token",
        Value:    generateSessionToken(), // Your token generation logic
        Path:     "/",
        Domain:   "example.com",
        Expires:  time.Now().Add(24 * time.Hour),
        MaxAge:   86400, // 24 hours in seconds
        HttpOnly: true,  // Prevents JavaScript access
        Secure:   true,  // Only send over HTTPS
        SameSite: http.SameSiteStrictMode,
    }
    http.SetCookie(w, sessionCookie)

    w.Write([]byte("Cookies set successfully"))
}

func generateSessionToken() string {
    // Implement secure token generation
    return "secure-random-token"
}

Deleting Cookies

Remove cookies by setting them with a past expiration date:

func deleteCookieHandler(w http.ResponseWriter, r *http.Request) {
    cookie := &http.Cookie{
        Name:    "session_token",
        Value:   "",
        Path:    "/",
        Expires: time.Unix(0, 0), // Past date
        MaxAge:  -1,
    }
    http.SetCookie(w, cookie)

    w.Write([]byte("Cookie deleted"))
}

Session Management

Using gorilla/sessions

Install the gorilla/sessions package for advanced session management:

go get github.com/gorilla/sessions

Basic Session Implementation

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/gorilla/sessions"
)

// Initialize session store with secret key
var store = sessions.NewCookieStore([]byte("your-32-byte-secret-key-here"))

func init() {
    // Configure session options
    store.Options = &sessions.Options{
        Path:     "/",
        MaxAge:   3600, // 1 hour
        HttpOnly: true,
        Secure:   true, // Set to false for development without HTTPS
        SameSite: http.SameSiteStrictMode,
    }
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    username := r.FormValue("username")
    password := r.FormValue("password")

    // Validate credentials (implement your authentication logic)
    if validateCredentials(username, password) {
        session, err := store.Get(r, "user-session")
        if err != nil {
            http.Error(w, "Session error", http.StatusInternalServerError)
            return
        }

        // Set session values
        session.Values["authenticated"] = true
        session.Values["username"] = username
        session.Values["login_time"] = time.Now()

        // Save session
        if err := session.Save(r, w); err != nil {
            http.Error(w, "Could not save session", http.StatusInternalServerError)
            return
        }

        http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
    } else {
        http.Error(w, "Invalid credentials", http.StatusUnauthorized)
    }
}

func dashboardHandler(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "user-session")
    if err != nil {
        http.Error(w, "Session error", http.StatusInternalServerError)
        return
    }

    // Check authentication
    auth, ok := session.Values["authenticated"].(bool)
    if !ok || !auth {
        http.Redirect(w, r, "/login", http.StatusSeeOther)
        return
    }

    username := session.Values["username"].(string)
    loginTime := session.Values["login_time"].(time.Time)

    fmt.Fprintf(w, "Welcome %s! Logged in at: %s", username, loginTime.Format("2006-01-02 15:04:05"))
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    session, err := store.Get(r, "user-session")
    if err != nil {
        http.Error(w, "Session error", http.StatusInternalServerError)
        return
    }

    // Clear session values
    session.Values["authenticated"] = false
    delete(session.Values, "username")
    delete(session.Values, "login_time")

    // Set MaxAge to -1 to delete the session cookie
    session.Options.MaxAge = -1

    if err := session.Save(r, w); err != nil {
        http.Error(w, "Could not clear session", http.StatusInternalServerError)
        return
    }

    http.Redirect(w, r, "/login", http.StatusSeeOther)
}

func validateCredentials(username, password string) bool {
    // Implement your authentication logic
    return username == "admin" && password == "password"
}

Session Middleware

Create middleware for protecting routes:

func requireAuth(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        session, err := store.Get(r, "user-session")
        if err != nil {
            http.Error(w, "Session error", http.StatusInternalServerError)
            return
        }

        auth, ok := session.Values["authenticated"].(bool)
        if !ok || !auth {
            http.Redirect(w, r, "/login", http.StatusSeeOther)
            return
        }

        // Add user info to request context if needed
        next(w, r)
    }
}

func main() {
    http.HandleFunc("/login", loginHandler)
    http.HandleFunc("/logout", logoutHandler)
    http.HandleFunc("/dashboard", requireAuth(dashboardHandler))

    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Advanced Session Storage

Redis Session Store

For production applications, use Redis for session storage:

import (
    "github.com/gorilla/sessions"
    "github.com/rbcervilla/redisstore/v9"
    "github.com/redis/go-redis/v9"
)

func initRedisStore() *redisstore.RedisStore {
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    store, err := redisstore.NewRedisStore(context.Background(), client)
    if err != nil {
        log.Fatal("Failed to create Redis store:", err)
    }

    store.KeyPrefix("session_")
    store.Options(sessions.Options{
        Path:     "/",
        MaxAge:   3600,
        HttpOnly: true,
        Secure:   true,
    })

    return store
}

Security Best Practices

Secure Cookie Configuration

Always configure cookies securely in production:

store.Options = &sessions.Options{
    Path:     "/",
    MaxAge:   3600,
    HttpOnly: true,                    // Prevent XSS attacks
    Secure:   true,                    // HTTPS only
    SameSite: http.SameSiteStrictMode, // CSRF protection
}

Session Token Generation

Generate cryptographically secure session tokens:

import (
    "crypto/rand"
    "encoding/base64"
)

func generateSecureToken(length int) (string, error) {
    bytes := make([]byte, length)
    if _, err := rand.Read(bytes); err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(bytes), nil
}

Session Timeout Management

Implement session timeout for security:

func checkSessionTimeout(session *sessions.Session) bool {
    lastActivity, ok := session.Values["last_activity"].(time.Time)
    if !ok {
        return false
    }

    timeout := 30 * time.Minute
    return time.Since(lastActivity) > timeout
}

func updateLastActivity(session *sessions.Session) {
    session.Values["last_activity"] = time.Now()
}

Testing Sessions

Test session functionality with httptest:

func TestSessionLogin(t *testing.T) {
    req, _ := http.NewRequest("POST", "/login", strings.NewReader("username=admin&password=password"))
    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(loginHandler)
    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusSeeOther {
        t.Errorf("Expected status %d, got %d", http.StatusSeeOther, status)
    }

    // Check if session cookie was set
    cookies := rr.Result().Cookies()
    if len(cookies) == 0 {
        t.Error("Expected session cookie to be set")
    }
}

This comprehensive approach to handling HTTP sessions and cookies in Go provides both security and functionality for web applications and scraping tools that need to maintain state across requests.

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon