Skip to content

Sessions

λ Cosmos provides server-side session management with automatic persistence, expiration handling, and session regeneration for security.

type Session struct {
ID string
Data map[string]any
ExpiresAt time.Time
}
type SessionDriver interface {
Get(ctx context.Context, id string) (Session, error)
Save(ctx context.Context, session Session, ttl time.Duration) error
Delete(ctx context.Context, id string) error
}

Set up sessions with the session middleware and a cache-backed driver:

import (
"github.com/studiolambda/cosmos/framework"
"github.com/studiolambda/cosmos/framework/cache"
"github.com/studiolambda/cosmos/framework/session"
"github.com/studiolambda/cosmos/framework/middleware"
"github.com/studiolambda/cosmos/contract/request"
)
app := framework.New()
// Use in-memory cache as the session store
store := cache.NewMemory(2*time.Hour, 10*time.Minute)
driver := session.NewCacheDriver(store)
app.Use(middleware.Recover())
app.Use(session.Middleware(driver))
app.Get("/profile", func(w http.ResponseWriter, r *http.Request) error {
s := request.MustSession(r)
name, _ := s.Get("name")
return response.JSON(w, http.StatusOK, map[string]any{
"name": name,
})
})
app.Post("/login", func(w http.ResponseWriter, r *http.Request) error {
s := request.MustSession(r)
s.Put("name", "Alice")
s.Put("authenticated", true)
// Regenerate the session ID after authentication to prevent fixation attacks
s.Regenerate()
return response.Status(w, http.StatusOK)
})

The middleware handles the full session lifecycle:

  1. Load — Reads the session ID from the cookie and loads the session from the driver. If no cookie exists or the session is not found, a new session is created.
  2. Inject — Places the session into the request context.
  3. Persist — Before the response header is written, checks if the session needs saving:
    • If the session has expired, it regenerates the ID and extends the TTL.
    • If the session is about to expire (within the expiration delta), it extends the TTL.
    • If the session was regenerated, it deletes the old session from the store.
    • If any data changed, it saves the session and sets the cookie.
session.Middleware(driver)

Uses these defaults:

  • Cookie name: cosmos.session
  • Path: /
  • Secure: true
  • HttpOnly: true (always)
  • SameSite: Lax
  • TTL: 2 hours
  • Max lifetime: 24 hours (absolute session age limit)
  • Expiration delta: 15 minutes (extends when this close to expiring)
session.MiddlewareWith(driver, session.MiddlewareOptions{
Name: "my_session",
Path: "/",
Domain: "example.com",
Secure: true,
SameSite: http.SameSiteStrictMode,
Partitioned: true,
TTL: 24 * time.Hour,
MaxLifetime: 48 * time.Hour,
ExpirationDelta: 1 * time.Hour,
Key: contract.SessionKey,
ErrorHandler: func(err error) {
slog.Error("session error", "err", err)
},
})
s := request.MustSession(r)
value, exists := s.Get("user_id")
if !exists {
// key not in session
}
s.Put("user_id", "123")
s.Put("role", "admin")
s.Delete("temporary_flag")
s.Clear()

Removes all session data but keeps the session ID and expiration intact.

err := s.Regenerate()

Generates a new session ID using crypto/rand while preserving all session data. The middleware automatically deletes the old session from the store. Use this after authentication events to prevent session fixation attacks.

s.Extend(time.Now().Add(24 * time.Hour))
s.HasExpired() // true if past expiration time
s.ExpiresSoon(15*time.Minute) // true if expiring within 15 minutes
s.ExpiresAt() // returns the expiration time.Time
s.HasChanged() // true if any Put/Delete/Clear/Extend/Regenerate was called
s.HasRegenerated() // true if Regenerate() was called
s.MarkAsUnchanged() // reset the change flag (prevents persistence)

The middleware only persists the session and sets the cookie when HasChanged() is true. Call MarkAsUnchanged() if you want to suppress persistence for a particular request.

λ Cosmos applies several security measures to sessions:

  • Cryptographic IDs: Session IDs are generated using crypto/rand with 256-bit entropy, encoded as base64url (43 characters). They do not contain timestamps or sequential data.
  • ID validation: Malformed session IDs are rejected before cache lookup, preventing key injection attacks.
  • Absolute lifetime: The MaxLifetime option (default 24 hours) enforces a hard limit on session age. When reached, the session ID is regenerated regardless of activity. This prevents indefinite session extension through repeated use.
  • Secure defaults: When using MiddlewareWith with zero-value options, the middleware applies Secure: true and SameSite: Lax rather than leaving them unset.

Always call s.Regenerate() after authentication to prevent session fixation attacks.

The built-in session.CacheDriver stores sessions in any contract.Cache implementation:

// Using in-memory cache
driver := session.NewCacheDriver(cache.NewMemory(2*time.Hour, 10*time.Minute))
// Using Redis
driver := session.NewCacheDriver(cache.NewRedis(&cache.RedisOptions{
Addr: "localhost:6379",
}))

Sessions are stored with a key prefix (default: cosmos.sessions). Customize it with:

driver := session.NewCacheDriverWith(store, session.CacheDriverOptions{
prefix: "myapp.sessions",
})

Implement the contract.SessionDriver interface to store sessions in any backend (database, file system, etc.).