Advanced Session Management¶
Blueprint provides a cookie-based session management system with encryption, multiple storage backends, and security features.
Note: when using custom types with sessions, always register the types for serialization/deserialization with gob.Register()
Architecture Overview¶
The session system consists of five main components:
- SessionData (
session_data.go): Core data structure with typed accessors and identity management - SessionManager (
middleware.go): Gin middleware for automatic session lifecycle management - SessionStore (
store.go): Interface for session storage backends;Storeis the built-in implementation with encryption and automatic cleanup - Config (
config.go): Comprehensive configuration with security defaults and validation - Marshaller (
marshaller.go): Pluggable serialization interface supporting GOB and JSON formats
Features¶
Core Session Features¶
- Flexible Storage: Support for any KV backend (memory, Redis, or custom implementations)
- Session Encryption: Optional AES256GCM encryption for cookie data
- Type-safe Access: Typed getters for common data types (string, int, bool)
- Identity Management: Built-in user identity support with dedicated methods
- Flash Messages: One-time messages that persist across requests
- Session Regeneration: Built-in protection against session fixation attacks
- Automatic Cleanup: Configurable cleanup intervals for expired sessions
- Dual Expiration: Both absolute expiration and idle timeout support
- Pluggable Serialization: Choice between GOB (default) and JSON marshallers, or custom implementations
Security Features¶
- Cookie Security: HttpOnly, Secure, and SameSite configuration
- Session Fixation Protection: Regenerate session IDs on authentication
- Encryption Support: Optional AES256GCM encryption for sensitive session data
- Secure Defaults: Production-ready security settings out of the box
Session Setup¶
Option 1: Memory-based Sessions¶
package main
import (
"github.com/oddbit-project/blueprint/provider/httpserver"
"github.com/oddbit-project/blueprint/provider/httpserver/session"
"github.com/oddbit-project/blueprint/provider/kv"
"github.com/oddbit-project/blueprint/log"
)
func main() {
logger := log.New("session-app")
// Server setup
config := httpserver.NewServerConfig()
server, err := httpserver.NewServer(config, logger)
if err != nil {
logger.Fatal(err, "failed to create server")
}
// Session configuration
sessionConfig := session.NewConfig()
sessionConfig.CookieName = "my_session"
sessionConfig.ExpirationSeconds = 3600 // 1 hour
sessionConfig.IdleTimeoutSeconds = 1800 // 30 minutes
// Memory backend for development
backend := kv.NewMemoryKV()
// Setup session middleware
sessionManager, err := server.UseSession(sessionConfig, backend, logger)
if err != nil {
logger.Fatal(err, "failed to setup sessions")
}
setupRoutes(server)
server.Start()
}
Option 2: Redis-based Sessions¶
func setupRedisSession(server *httpserver.Server, logger *log.Logger) {
// Redis configuration
redisConfig := redis.NewConfig()
redisConfig.Address = "redis:6379"
redisConfig.DB = 1
// Create Redis client
redisClient, err := redis.NewClient(redisConfig)
if err != nil {
logger.Fatal(err, "failed to connect to Redis")
}
// Session configuration with encryption
sessionConfig := session.NewConfig()
sessionConfig.ExpirationSeconds = 7200 // 2 hours
sessionConfig.EncryptionKey = secure.DefaultCredentialConfig{
PasswordEnvVar: "SESSION_ENCRYPTION_KEY",
}
// Setup session middleware with Redis
sessionManager, err := server.UseSession(sessionConfig, redisClient, logger)
if err != nil {
logger.Fatal(err, "failed to setup Redis sessions")
}
}
Option 3: Manual Session Setup (Advanced)¶
func setupAdvancedSession(server *httpserver.Server, logger *log.Logger) {
// Custom backend
backend := kv.NewMemoryKV()
// Session configuration
sessionConfig := session.NewConfig()
sessionConfig.Secure = true
sessionConfig.HttpOnly = true
sessionConfig.SameSite = int(http.SameSiteStrictMode)
sessionConfig.CleanupIntervalSeconds = 300
// Create store manually
sessionStore, err := session.NewStore(sessionConfig, backend, logger)
if err != nil {
logger.Fatal(err, "failed to create session store")
}
// Create manager manually with options
sessionManager, err := session.NewManager(sessionConfig,
session.ManagerWithStore(sessionStore),
session.ManagerWithLogger(logger))
if err != nil {
logger.Fatal(err, "failed to create session manager")
}
// Add middleware
server.Route().Use(sessionManager.Middleware())
}
func setupSessionWithJSONMarshaller(server *httpserver.Server, logger *log.Logger) {
backend := kv.NewMemoryKV()
sessionConfig := session.NewConfig()
// Create store with JSON marshaller
sessionStore, err := session.NewStore(sessionConfig, backend, logger)
if err != nil {
logger.Fatal(err, "failed to create session store")
}
// Use JSON marshaller instead of default GOB
sessionStore.WithMarshaller(session.NewJSONMarshaller())
sessionManager, err := session.NewManager(sessionConfig,
session.ManagerWithStore(sessionStore),
session.ManagerWithLogger(logger))
if err != nil {
logger.Fatal(err, "failed to create session manager")
}
server.Route().Use(sessionManager.Middleware())
}
SessionStore Interface¶
The SessionStore interface allows custom session storage implementations (e.g., Redis, SQL database). The built-in Store struct satisfies this interface.
type SessionStore interface {
Get(id string) (*SessionData, error)
Set(id string, session *SessionData) error
Delete(id string) error
Generate() (*SessionData, string)
StartCleanup()
Close()
}
A custom Get must return session.ErrSessionNotFound for an unknown ID and session.ErrSessionExpired
for an expired session (matched with errors.Is): the middleware treats those two as normal, and logs a
warning for any other error (see Unreadable Sessions). A store that returns, say,
sql.ErrNoRows for an unknown ID would log one warning per request carrying an unknown cookie.
ManagerWithStore() accepts any SessionStore implementation:
// Use built-in Store
sessionStore, _ := session.NewStore(config, backend, logger)
manager, _ := session.NewManager(config,
session.ManagerWithStore(sessionStore), // *Store satisfies SessionStore
session.ManagerWithLogger(logger))
// Or use a custom implementation
customStore := &MyCustomSessionStore{db: database}
manager, _ := session.NewManager(config,
session.ManagerWithStore(customStore),
session.ManagerWithLogger(logger))
Session Configuration¶
Complete Configuration Reference¶
type Config struct {
// Cookie configuration
CookieName string `json:"cookieName"` // Cookie name (default: "blueprint_session")
Domain string `json:"domain"` // Cookie domain scope
Path string `json:"path"` // Cookie path scope (default: "/")
// Security configuration
Secure bool `json:"secure"` // HTTPS only (default: true)
HttpOnly bool `json:"httpOnly"` // No JS access (default: true)
SameSite int `json:"sameSite"` // CSRF protection (default: Strict)
// Expiration configuration
ExpirationSeconds int `json:"expirationSeconds"` // Session lifetime (default: 1800)
IdleTimeoutSeconds int `json:"idleTimeoutSeconds"` // Idle timeout (default: 900)
CleanupIntervalSeconds int `json:"cleanupIntervalSeconds"` // Cleanup frequency (default: 300)
// Encryption configuration (optional)
EncryptionKey secure.DefaultCredentialConfig `json:"encryptionKey"`
}
Security Defaults¶
const (
DefaultSessionCookieName = "blueprint_session"
DefaultSessionExpiration = 1800 // 30 minutes
DefaultSessionIdleTimeout = 900 // 15 minutes
DefaultSecure = true // HTTPS only
DefaultHttpOnly = true // No JS access
DefaultSameSite = http.SameSiteStrictMode
DefaultCleanupInterval = 300 // 5 minutes
)
Production Configuration Example¶
{
"session": {
"cookieName": "app_session",
"expirationSeconds": 7200,
"idleTimeoutSeconds": 3600,
"secure": true,
"httpOnly": true,
"sameSite": 1,
"domain": ".example.com",
"path": "/",
"encryptionKey": {
"passwordEnvVar": "SESSION_ENCRYPTION_KEY"
},
"cleanupIntervalSeconds": 300
}
}
Working with Sessions¶
Session Data Structure¶
type SessionData struct {
Values map[string]any `json:"values"`
LastAccessed time.Time `json:"lastAccessed"`
Created time.Time `json:"created"`
ID string `json:"id"`
}
Basic Session Operations¶
func sessionHandler(c *gin.Context) {
// Get session from context
sess := session.Get(c)
// Store values
sess.Set("user_id", 123)
sess.Set("username", "john_doe")
sess.Set("preferences", map[string]any{
"theme": "dark",
"language": "en",
})
// Retrieve values with type safety
userID, ok := sess.GetInt("user_id")
if ok {
logger.Info("User ID", "id", userID)
}
username, ok := sess.GetString("username")
if ok {
logger.Info("Username", "username", username)
}
// Check existence
if sess.Has("preferences") {
prefs, _ := sess.Get("preferences")
logger.Info("User preferences", "prefs", prefs)
}
// Delete values
sess.Delete("temporary_data")
c.JSON(200, gin.H{
"session_id": sess.ID,
"user_id": userID,
"username": username,
})
}
Identity Management¶
// Custom identity type
type UserIdentity struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Roles []string `json:"roles"`
}
// Register with GOB for serialization
func init() {
gob.Register(&UserIdentity{})
}
func loginHandler(c *gin.Context) {
var loginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&loginReq); err != nil {
c.JSON(400, gin.H{"error": "Invalid request"})
return
}
// Authenticate user (your authentication logic)
user, err := authenticateUser(loginReq.Username, loginReq.Password)
if err != nil {
c.JSON(401, gin.H{"error": "Invalid credentials"})
return
}
// Get session
sess := session.Get(c)
// Set user identity
identity := &UserIdentity{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Roles: user.Roles,
}
sess.SetIdentity(identity)
// Regenerate session ID for security
sessionManager.Regenerate(c)
c.JSON(200, gin.H{"message": "Login successful"})
}
func getCurrentUser(c *gin.Context) *UserIdentity {
sess := session.Get(c)
identity, ok := sess.GetIdentity()
if !ok {
return nil
}
user, ok := identity.(*UserIdentity)
if !ok {
return nil
}
return user
}
func protectedHandler(c *gin.Context) {
user := getCurrentUser(c)
if user == nil {
c.JSON(401, gin.H{"error": "Not authenticated"})
return
}
c.JSON(200, gin.H{
"user": user,
"message": "Access granted",
})
}
func logoutHandler(c *gin.Context) {
sess := session.Get(c)
sess.DeleteIdentity()
// Clear entire session
sessionManager.Clear(c)
c.JSON(200, gin.H{"message": "Logged out"})
}
Flash Messages¶
func setFlashMessage(c *gin.Context) {
sess := session.Get(c)
// Set flash message
sess.FlashString("Operation completed successfully!")
c.Redirect(302, "/dashboard")
}
func displayFlashMessage(c *gin.Context) {
sess := session.Get(c)
// Get simple flash message
message, ok := sess.GetFlashString()
if ok {
c.HTML(200, "dashboard.html", gin.H{
"flash_message": message,
})
return
}
// No flash messages
c.HTML(200, "dashboard.html", gin.H{})
}
Security Operations¶
Session Regeneration (IMPORTANT)¶
func loginHandler(c *gin.Context) {
// ... authentication logic ...
sess := session.Get(c)
sess.SetIdentity(user)
// IMPORTANT: Regenerate session ID after authentication
// This prevents session fixation attacks
sessionManager.Regenerate(c)
c.JSON(200, gin.H{"message": "Login successful"})
}
func elevatePrivilegesHandler(c *gin.Context) {
// When user gains elevated privileges, regenerate session
sess := session.Get(c)
// Update user role
user := getCurrentUser(c)
user.Roles = append(user.Roles, "admin")
sess.SetIdentity(user)
// Regenerate session for security
sessionManager.Regenerate(c)
c.JSON(200, gin.H{"message": "Privileges elevated"})
}
Session Clearing¶
func logoutHandler(c *gin.Context) {
// Option 1: Clear entire session
sessionManager.Clear(c)
c.JSON(200, gin.H{"message": "Logged out"})
}
func partialLogoutHandler(c *gin.Context) {
sess := session.Get(c)
// Option 2: Clear only identity but keep other session data
sess.DeleteIdentity()
// Keep non-sensitive data like preferences
c.JSON(200, gin.H{"message": "Logged out, preferences retained"})
}
Session Encryption¶
Encryption Configuration¶
// Environment variable approach
sessionConfig.EncryptionKey = secure.DefaultCredentialConfig{
PasswordEnvVar: "SESSION_ENCRYPTION_KEY",
}
// File-based key
sessionConfig.EncryptionKey = secure.DefaultCredentialConfig{
PasswordFile: "/etc/secrets/session-key",
}
// Direct key (not recommended for production)
sessionConfig.EncryptionKey = secure.DefaultCredentialConfig{
Password: "your-32-byte-encryption-key-here",
}
Key Generation¶
# Generate a secure 32-byte key
openssl rand -base64 32
# Set as environment variable
export SESSION_ENCRYPTION_KEY="generated-key-here"
Unreadable Sessions¶
When a request's session cookie cannot be turned into a session, the middleware starts a new one and
sets a new cookie. An unknown session ID (ErrSessionNotFound) and an expired session
(ErrSessionExpired) are normal and not logged. Any other failure is logged as a warning
("Failed to read session, starting a new one", with the underlying error in the error field). The
message never contains the session ID, but the error text from the backend or marshaller is logged as
is, so a custom kv.KV or SessionStore must not put the key in its errors. Causes:
- decryption failures (
secure.ErrAuthenticationFailed,secure.ErrDataTooShort): the stored data was tampered with or corrupted, or was written with a differentEncryptionKey(for example after a key rotation, or instances configured with different keys); - unmarshalling failures (a corrupt record, or a marshaller change);
- backend read errors (for example Redis being unreachable);
- a cookie that names a key another application stored in the same backend. With the Redis backend's
default empty
KeyPrefixthe cookie value is the raw Redis key, so a client can cause these warnings on a shared Redis database: use aKeyPrefixthat only sessions use (for example"session:"; not one shared with the HMAC nonce store or otherkv.KVusers) or a dedicated database.
A record the session store wrote is left in the backend until its TTL expires.
Session Marshallers¶
The session system supports pluggable serialization through the Marshaller interface, allowing you to choose the best format for your application's needs.
Marshaller Interface¶
type Marshaller interface {
MarshalSession(session *SessionData) ([]byte, error)
UnmarshalSession(data []byte) (*SessionData, error)
}
Built-in Marshallers¶
GOB Marshaller (Default)¶
The GOB marshaller is the default serialization format. It's binary, efficient, and supports complex Go types.
// Default - using GOB marshaller
sessionStore, err := session.NewStore(sessionConfig, backend, logger)
// Explicitly using GOB marshaller
sessionStore.WithMarshaller(session.NewGobMarshaller())
Advantages: - Efficient binary format - Supports complex Go types (structs, interfaces, channels) - Smaller size compared to JSON - Faster serialization/deserialization
Disadvantages: - Go-specific format (not interoperable with other languages) - Requires type registration for custom types
Type Registration for GOB:
func init() {
// Register custom types for GOB serialization
gob.Register(&UserIdentity{})
gob.Register(map[string]interface{}{})
gob.Register([]CustomType{})
}
JSON Marshaller¶
The JSON marshaller provides human-readable serialization and better interoperability.
// Using JSON marshaller
sessionStore, err := session.NewStore(sessionConfig, backend, logger)
sessionStore.WithMarshaller(session.NewJSONMarshaller())
Advantages: - Human-readable format - Interoperable with other languages/systems - Easy debugging (can inspect stored sessions) - No type registration required
Disadvantages: - Larger size compared to GOB - Slower serialization/deserialization - Limited type support (no complex Go types) - Loss of type information for interfaces
Custom Marshaller Implementation¶
You can implement your own marshaller for specific requirements:
package main
import (
"github.com/vmihailenco/msgpack/v5"
"github.com/oddbit-project/blueprint/provider/httpserver/session"
)
// MessagePack marshaller implementation
type msgpackMarshaller struct{}
func NewMsgPackMarshaller() session.Marshaller {
return &msgpackMarshaller{}
}
func (m *msgpackMarshaller) MarshalSession(session *session.SessionData) ([]byte, error) {
return msgpack.Marshal(session)
}
func (m *msgpackMarshaller) UnmarshalSession(data []byte) (*session.SessionData, error) {
var session session.SessionData
err := msgpack.Unmarshal(data, &session)
if err != nil {
return nil, err
}
return &session, nil
}
// Usage
func setupSessionWithMsgPack(server *httpserver.Server, logger *log.Logger) {
backend := kv.NewMemoryKV()
sessionConfig := session.NewConfig()
sessionStore, err := session.NewStore(sessionConfig, backend, logger)
if err != nil {
logger.Fatal(err, "failed to create session store")
}
// Use custom MessagePack marshaller
sessionStore.WithMarshaller(NewMsgPackMarshaller())
// Continue with session setup...
}
Choosing a Marshaller¶
| Use Case | Recommended Marshaller | Reason |
|---|---|---|
| Go-only application | GOB (default) | Best performance, full type support |
| Microservices/API | JSON | Interoperability, debugging |
| High-performance | GOB or Custom (MessagePack) | Minimal overhead |
| Cross-language | JSON | Universal format |
| Debug/Development | JSON | Human-readable storage |
Marshaller Performance Comparison¶
Approximate benchmarks for a typical session with user data:
| Marshaller | Serialization | Deserialization | Size |
|---|---|---|---|
| GOB | ~500 ns/op | ~600 ns/op | ~200 bytes |
| JSON | ~1200 ns/op | ~1500 ns/op | ~350 bytes |
| MessagePack* | ~400 ns/op | ~500 ns/op | ~180 bytes |
*Custom implementation required
Backend Storage Options¶
Memory Backend (Development)¶
backend := kv.NewMemoryKV()
// Pros: Fast, simple setup
// Cons: Not persistent, single instance only
Redis Backend (Production)¶
redisConfig := redis.NewConfig()
redisConfig.Address = "redis-cluster:6379"
redisConfig.Password = "redis-password"
redisConfig.DB = 1
backend, err := redis.NewClient(redisConfig)
// Pros: Distributed, persistent, scalable
// Cons: Network latency, additional infrastructure
Redis expires session keys through the TTL set on each write, so the store's periodic Prune() call
is a no-op for the Redis backend.
Custom Backend¶
// CustomKV must implement all methods of kv.KV
var _ kv.KV = (*CustomKV)(nil)
type CustomKV struct {
// Your implementation
}
func (c *CustomKV) Set(key string, value []byte) error {
// Store without TTL
return nil
}
func (c *CustomKV) SetTTL(key string, value []byte, ttl time.Duration) error {
// Store with TTL
return nil
}
func (c *CustomKV) Get(key string) ([]byte, error) {
// Retrieve value; return nil, nil if the key is missing or expired
return nil, nil
}
func (c *CustomKV) Delete(key string) error {
// Delete value
return nil
}
func (c *CustomKV) Prune() error {
// Clean up expired entries
return nil
}
Example: Secure Web Application¶
package main
import (
"encoding/gob"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/oddbit-project/blueprint/provider/httpserver"
"github.com/oddbit-project/blueprint/provider/httpserver/auth"
"github.com/oddbit-project/blueprint/provider/httpserver/session"
"github.com/oddbit-project/blueprint/provider/httpserver/security"
"github.com/oddbit-project/blueprint/provider/kv"
"github.com/oddbit-project/blueprint/crypt/secure"
"github.com/oddbit-project/blueprint/log"
"golang.org/x/time/rate"
)
type UserIdentity struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Roles []string `json:"roles"`
}
func init() {
// Register custom types for GOB serialization
gob.Register(&UserIdentity{})
}
func main() {
logger := log.New("secure-web-app")
// Server configuration
serverConfig := httpserver.NewServerConfig()
serverConfig.Host = "localhost"
serverConfig.Port = 8443
serverConfig.ServerConfig.TLSEnable = true
serverConfig.ServerConfig.TLSCert = "server.crt"
serverConfig.ServerConfig.TLSKey = "server.key"
server, err := httpserver.NewServer(serverConfig, logger)
if err != nil {
logger.Fatal(err, "failed to create server")
}
// Session configuration
sessionConfig := session.NewConfig()
sessionConfig.CookieName = "secure_session"
sessionConfig.ExpirationSeconds = 7200 // 2 hours
sessionConfig.IdleTimeoutSeconds = 1800 // 30 minutes
sessionConfig.Secure = true
sessionConfig.HttpOnly = true
sessionConfig.SameSite = int(http.SameSiteStrictMode)
sessionConfig.EncryptionKey = secure.DefaultCredentialConfig{
PasswordEnvVar: "SESSION_ENCRYPTION_KEY",
}
// Setup session store
backend := kv.NewMemoryKV() // Use Redis in production
sessionManager, err := server.UseSession(sessionConfig, backend, logger)
if err != nil {
logger.Fatal(err, "failed to setup sessions")
}
// Security headers
securityConfig := security.DefaultSecurityConfig()
securityConfig.CSP = "default-src 'self'; script-src 'self' 'nonce-{nonce}'"
server.Route().Use(security.SecurityMiddleware(securityConfig))
// CSRF protection
server.Route().Use(security.CSRFProtection())
// Rate limiting
rlHandler, rlLimiter := security.RateLimitMiddleware(rate.Every(time.Second), 10)
defer rlLimiter.Stop()
server.Route().Use(rlHandler)
// Routes
setupRoutes(server, sessionManager)
// Start server
if err := server.Start(); err != nil {
logger.Fatal(err, "failed to start server")
}
}
func setupRoutes(server *httpserver.Server, sessionManager *session.Manager) {
router := server.Route()
// Public routes
router.GET("/", homeHandler)
router.GET("/login", loginFormHandler)
router.POST("/login", loginHandler(sessionManager))
router.GET("/register", registerFormHandler)
router.POST("/register", registerHandler)
// Protected routes
protected := router.Group("/dashboard")
protected.Use(auth.AuthMiddleware(auth.NewAuthSession(&UserIdentity{})))
{
protected.GET("/", dashboardHandler)
protected.GET("/profile", profileHandler)
protected.POST("/profile", updateProfileHandler)
protected.POST("/logout", logoutHandler(sessionManager))
}
// Admin routes
admin := router.Group("/admin")
admin.Use(auth.AuthMiddleware(auth.NewAuthSession(&UserIdentity{})))
admin.Use(requireRole("admin"))
{
admin.GET("/users", listUsersHandler)
admin.DELETE("/users/:id", deleteUserHandler)
}
}
func loginHandler(sessionManager *session.Manager) gin.HandlerFunc {
return func(c *gin.Context) {
var req struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "Invalid request"})
return
}
// Authenticate user
user, err := authenticateUser(req.Username, req.Password)
if err != nil {
c.JSON(401, gin.H{"error": "Invalid credentials"})
return
}
// Get session and set identity
sess := session.Get(c)
identity := &UserIdentity{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Roles: user.Roles,
}
sess.SetIdentity(identity)
// Set flash message
sess.FlashString("Welcome back, " + user.Username + "!")
// Regenerate session ID for security
sessionManager.Regenerate(c)
c.JSON(200, gin.H{
"message": "Login successful",
"redirect": "/dashboard",
})
}
}
func dashboardHandler(c *gin.Context) {
sess := session.Get(c)
user := getCurrentUser(c)
// Get flash message
flashMessage, _ := sess.GetFlashString()
c.HTML(200, "dashboard.html", gin.H{
"user": user,
"flash": flashMessage,
"csrf_token": security.GenerateCSRFToken(c),
})
}
func logoutHandler(sessionManager *session.Manager) gin.HandlerFunc {
return func(c *gin.Context) {
// Clear session
sessionManager.Clear(c)
c.JSON(200, gin.H{
"message": "Logged out successfully",
"redirect": "/",
})
}
}
func requireRole(role string) gin.HandlerFunc {
return func(c *gin.Context) {
user := getCurrentUser(c)
if user == nil {
c.AbortWithStatusJSON(401, gin.H{"error": "Not authenticated"})
return
}
hasRole := false
for _, userRole := range user.Roles {
if userRole == role {
hasRole = true
break
}
}
if !hasRole {
c.AbortWithStatusJSON(403, gin.H{"error": "Insufficient privileges"})
return
}
c.Next()
}
}
func getCurrentUser(c *gin.Context) *UserIdentity {
sess := session.Get(c)
identity, ok := sess.GetIdentity()
if !ok {
return nil
}
user, ok := identity.(*UserIdentity)
if !ok {
return nil
}
return user
}
Best Practices¶
Security Best Practices¶
-
Always use HTTPS in production
-
Regenerate session ID after authentication
-
Use encryption for sensitive data
-
Set appropriate timeouts
-
Use SameSite cookies
Performance Best Practices¶
- Choose appropriate backend
- Memory: Development and single-instance applications
-
Redis: Production and distributed applications
-
Configure cleanup intervals
-
Minimize session data
- Store only essential user information
-
Use references to database records instead of full objects
-
Register custom types with GOB
Development vs Production¶
func getSessionConfig(env string) *session.Config {
config := session.NewConfig()
if env == "production" {
config.Secure = true
config.HttpOnly = true
config.SameSite = int(http.SameSiteStrictMode)
config.ExpirationSeconds = 7200
config.EncryptionKey = secure.DefaultCredentialConfig{
PasswordEnvVar: "SESSION_ENCRYPTION_KEY",
}
} else {
config.Secure = false // Allow HTTP in development
config.ExpirationSeconds = 86400 // Longer for development
}
return config
}
Troubleshooting¶
Common Issues¶
- Sessions not persisting
- Check cookie security settings
- Verify backend connectivity
-
Ensure middleware order
-
"gob: type not registered" errors
- Register custom types with
gob.Register() -
Register in
init()function -
Session expiration issues
- Check system time synchronization
- Review timeout configurations
-
Monitor cleanup logs
-
Performance issues
- Monitor backend latency
- Optimize session data size
- Adjust cleanup intervals