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.