Skip to main content
โšก Calmops

Cookies and Sessions in Go

Cookies and Sessions in Go

Cookies and sessions are fundamental to web applications for maintaining user state. Go provides built-in support for cookies, and sessions can be implemented using various storage backends. This guide covers both.

Working with Cookies

Setting Cookies

package main

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

func setCookieHandler(w http.ResponseWriter, r *http.Request) {
	// Create a cookie
	cookie := &http.Cookie{
		Name:     "user_id",
		Value:    "12345",
		Path:     "/",
		Expires:  time.Now().Add(24 * time.Hour),
		HttpOnly: true,
		Secure:   false, // Set to true in production with HTTPS
	}

	// Set the cookie
	http.SetCookie(w, cookie)
	fmt.Fprintf(w, "Cookie set")
}

func main() {
	http.HandleFunc("/set-cookie", setCookieHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Reading Cookies

package main

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

func readCookieHandler(w http.ResponseWriter, r *http.Request) {
	// Read specific cookie
	cookie, err := r.Cookie("user_id")
	if err != nil {
		fmt.Fprintf(w, "Cookie not found")
		return
	}

	fmt.Fprintf(w, "User ID: %s", cookie.Value)
}

func readAllCookiesHandler(w http.ResponseWriter, r *http.Request) {
	// Read all cookies
	cookies := r.Cookies()
	for _, cookie := range cookies {
		fmt.Fprintf(w, "%s=%s\n", cookie.Name, cookie.Value)
	}
}

func main() {
	http.HandleFunc("/read-cookie", readCookieHandler)
	http.HandleFunc("/read-all", readAllCookiesHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}
package main

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

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// Session cookie (expires when browser closes)
		sessionCookie := &http.Cookie{
			Name:     "session",
			Value:    "abc123",
			Path:     "/",
			HttpOnly: true,
			Secure:   true,
			SameSite: http.SameSiteLaxMode,
		}

		// Persistent cookie (expires at specific time)
		persistentCookie := &http.Cookie{
			Name:     "remember_me",
			Value:    "[email protected]",
			Path:     "/",
			Expires:  time.Now().Add(30 * 24 * time.Hour),
			HttpOnly: true,
			Secure:   true,
		}

		http.SetCookie(w, sessionCookie)
		http.SetCookie(w, persistentCookie)

		fmt.Fprintf(w, "Cookies set")
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Deleting Cookies

package main

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

func deleteCookieHandler(w http.ResponseWriter, r *http.Request) {
	// Delete cookie by setting MaxAge to -1
	cookie := &http.Cookie{
		Name:   "user_id",
		Value:  "",
		Path:   "/",
		MaxAge: -1,
	}

	http.SetCookie(w, cookie)
	fmt.Fprintf(w, "Cookie deleted")
}

func main() {
	http.HandleFunc("/delete-cookie", deleteCookieHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Session Management

In-Memory Session Store

package main

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

type Session struct {
	ID        string
	Data      map[string]interface{}
	ExpiresAt time.Time
}

type SessionStore struct {
	sessions map[string]*Session
	mu       sync.RWMutex
}

func NewSessionStore() *SessionStore {
	return &SessionStore{
		sessions: make(map[string]*Session),
	}
}

func (ss *SessionStore) Create(id string) *Session {
	ss.mu.Lock()
	defer ss.mu.Unlock()

	session := &Session{
		ID:        id,
		Data:      make(map[string]interface{}),
		ExpiresAt: time.Now().Add(24 * time.Hour),
	}

	ss.sessions[id] = session
	return session
}

func (ss *SessionStore) Get(id string) *Session {
	ss.mu.RLock()
	defer ss.mu.RUnlock()

	session, ok := ss.sessions[id]
	if !ok || time.Now().After(session.ExpiresAt) {
		return nil
	}

	return session
}

func (ss *SessionStore) Delete(id string) {
	ss.mu.Lock()
	defer ss.mu.Unlock()

	delete(ss.sessions, id)
}

var store = NewSessionStore()

func loginHandler(w http.ResponseWriter, r *http.Request) {
	sessionID := "session_" + fmt.Sprintf("%d", time.Now().UnixNano())
	session := store.Create(sessionID)
	session.Data["user_id"] = "12345"
	session.Data["username"] = "john"

	cookie := &http.Cookie{
		Name:     "session_id",
		Value:    sessionID,
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
	}

	http.SetCookie(w, cookie)
	fmt.Fprintf(w, "Logged in")
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session_id")
	if err != nil {
		http.Error(w, "Not logged in", http.StatusUnauthorized)
		return
	}

	session := store.Get(cookie.Value)
	if session == nil {
		http.Error(w, "Session expired", http.StatusUnauthorized)
		return
	}

	username := session.Data["username"]
	fmt.Fprintf(w, "Welcome, %v", username)
}

func main() {
	http.HandleFunc("/login", loginHandler)
	http.HandleFunc("/profile", profileHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Session Middleware

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
)

type contextKey string

const sessionKey contextKey = "session"

func sessionMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		cookie, err := r.Cookie("session_id")
		if err != nil {
			http.Error(w, "Not logged in", http.StatusUnauthorized)
			return
		}

		session := store.Get(cookie.Value)
		if session == nil {
			http.Error(w, "Session expired", http.StatusUnauthorized)
			return
		}

		// Add session to context
		ctx := context.WithValue(r.Context(), sessionKey, session)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

func protectedHandler(w http.ResponseWriter, r *http.Request) {
	session := r.Context().Value(sessionKey).(*Session)
	fmt.Fprintf(w, "User: %v", session.Data["username"])
}

func main() {
	http.Handle("/protected", sessionMiddleware(http.HandlerFunc(protectedHandler)))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Practical Examples

User Authentication

package main

import (
	"crypto/sha256"
	"fmt"
	"log"
	"net/http"
	"time"
)

type User struct {
	ID       string
	Username string
	Password string // Should be hashed in production
}

var users = map[string]*User{
	"john": {ID: "1", Username: "john", Password: "password123"},
}

func hashPassword(password string) string {
	hash := sha256.Sum256([]byte(password))
	return fmt.Sprintf("%x", hash)
}

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

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

	user, ok := users[username]
	if !ok || user.Password != password {
		http.Error(w, "Invalid credentials", http.StatusUnauthorized)
		return
	}

	// Create session
	sessionID := fmt.Sprintf("session_%d", time.Now().UnixNano())
	session := store.Create(sessionID)
	session.Data["user_id"] = user.ID
	session.Data["username"] = user.Username

	// Set cookie
	cookie := &http.Cookie{
		Name:     "session_id",
		Value:    sessionID,
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
		MaxAge:   86400, // 24 hours
	}

	http.SetCookie(w, cookie)
	fmt.Fprintf(w, "Login successful")
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session_id")
	if err == nil {
		store.Delete(cookie.Value)
	}

	// Clear cookie
	cookie = &http.Cookie{
		Name:   "session_id",
		Value:  "",
		Path:   "/",
		MaxAge: -1,
	}

	http.SetCookie(w, cookie)
	fmt.Fprintf(w, "Logged out")
}

func main() {
	http.HandleFunc("/login", loginHandler)
	http.HandleFunc("/logout", logoutHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Remember Me Functionality

package main

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

func loginWithRememberHandler(w http.ResponseWriter, r *http.Request) {
	username := r.FormValue("username")
	rememberMe := r.FormValue("remember_me") == "on"

	// Create session
	sessionID := fmt.Sprintf("session_%d", time.Now().UnixNano())
	session := store.Create(sessionID)
	session.Data["username"] = username

	// Set session cookie
	sessionCookie := &http.Cookie{
		Name:     "session_id",
		Value:    sessionID,
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
	}

	http.SetCookie(w, sessionCookie)

	// Set remember me cookie if requested
	if rememberMe {
		rememberCookie := &http.Cookie{
			Name:     "remember_me",
			Value:    username,
			Path:     "/",
			Expires:  time.Now().Add(30 * 24 * time.Hour),
			HttpOnly: true,
			Secure:   true,
		}

		http.SetCookie(w, rememberCookie)
	}

	fmt.Fprintf(w, "Login successful")
}

func main() {
	http.HandleFunc("/login", loginWithRememberHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Best Practices

โœ… Good Practices

// Set HttpOnly flag to prevent JavaScript access
cookie := &http.Cookie{
	HttpOnly: true,
}

// Set Secure flag for HTTPS
cookie := &http.Cookie{
	Secure: true,
}

// Set SameSite to prevent CSRF
cookie := &http.Cookie{
	SameSite: http.SameSiteLaxMode,
}

// Use strong session IDs
sessionID := generateSecureRandomID()

// Expire sessions properly
session.ExpiresAt = time.Now().Add(24 * time.Hour)

// Hash passwords
hashedPassword := hashPassword(password)

โŒ Anti-Patterns

// Don't store sensitive data in cookies
cookie.Value = userPassword // Wrong!

// Don't use weak session IDs
sessionID := "session_1" // Predictable

// Don't forget HttpOnly flag
cookie := &http.Cookie{
	HttpOnly: false, // Vulnerable to XSS
}

// Don't store sessions in memory only
// Use persistent storage for production

// Don't ignore session expiration
// Always check expiry time

Resources

Summary

Cookies and sessions are essential for web applications:

  • Use cookies for client-side storage
  • Implement sessions for server-side state
  • Always set HttpOnly and Secure flags
  • Use SameSite to prevent CSRF
  • Expire sessions properly
  • Hash passwords and use strong session IDs

With these practices, you can build secure, user-friendly web applications in Go.

Comments