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))
}
Cookie Options
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
- Go http.Cookie Documentation
- HTTP State Management Mechanism
- OWASP Session Management
- Go Security Best Practices
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