Type-safe, production-ready session management for Crystal.
| Type-Safe | Define sessions as Crystal structs with compile-time guarantees |
| Multiple Backends | Cookie, Memory, Redis, or Clustered Redis—pick what fits |
| Battle-Tested Security | AES-256 encryption, HMAC-SHA256, PBKDF2, client binding |
| Production Resilience | Circuit breakers, retry logic, graceful degradation |
| 329+ Tests | Comprehensive coverage you can rely on |
Install (add to shard.yml):
dependencies:
session:
github: azutoolkit/sessionUse (< 30 seconds to your first session):
require "session"
# Define your session data
struct UserSession
include Session::SessionData
property user_id : Int64?
property username : String?
end
# Configure once
Session.configure do |config|
config.secret = ENV["SESSION_SECRET"]
config.provider = Session::MemoryStore(UserSession).provider
end
# Create and use sessions
session = Session.provider.create
session.data.user_id = 42
session.data.username = "alice"That's it. You're ready to build.
| Backend | Best For | Persistence | Multi-Node |
|---|---|---|---|
| CookieStore | Stateless apps, serverless | Client-side | Yes |
| MemoryStore | Development, single-server | None | No |
| RedisStore | Production deployments | Redis | Yes |
| ClusteredRedisStore | High-scale, multi-node | Redis + local cache | Yes |
# Cookie (stateless, client-side)
config.provider = Session::CookieStore(UserSession).provider
# Memory (development)
config.provider = Session::MemoryStore(UserSession).provider
# Redis (production)
config.provider = Session::RedisStore(UserSession).provider(client: Redis.new)
# Clustered Redis (high-scale production)
config.cluster.enabled = true
config.provider = Session::ClusteredRedisStore(UserSession).new(client: Redis.new)- Encryption — AES-256-CBC with random IV per operation
- Signing — HMAC-SHA256 to detect tampering
- Key Derivation — Optional PBKDF2 with configurable iterations
- Client Binding — Lock sessions to IP and/or User-Agent
- Size Protection — Automatic cookie size validation (4KB limit)
Session.configure do |config|
config.secret = ENV["SESSION_SECRET"] # 32+ chars recommended
config.use_kdf = true # Enable PBKDF2
config.kdf_iterations = 100_000 # OWASP recommended
config.bind_to_ip = true # Prevent session hijacking
config.bind_to_user_agent = true
end- Circuit Breaker — Fail fast when backends are down
- Retry Logic — Exponential backoff with jitter
- Compression — Gzip for large session payloads
Session.configure do |config|
config.circuit_breaker_enabled = true
config.circuit_breaker_config = Session::CircuitBreakerConfig.new(
failure_threshold: 5,
reset_timeout: 30.seconds
)
config.enable_retry = true
config.retry_config = Session::RetryConfig.new(
max_attempts: 3,
base_delay: 100.milliseconds,
backoff_multiplier: 2.0
)
endMulti-node session management with Redis Pub/Sub invalidation and local caching.
flowchart LR
subgraph Node_A["Node A"]
A_Cache["Local Cache"]
end
subgraph Node_B["Node B"]
B_Cache["Local Cache"]
end
Redis[("Redis")]
PubSub{{"Pub/Sub"}}
A_Cache <--> Redis
B_Cache <--> Redis
A_Cache -.->|invalidate| PubSub -.->|evict| B_Cache
Session.configure do |config|
config.cluster.enabled = true
config.cluster.node_id = ENV["POD_NAME"]? || UUID.random.to_s
config.cluster.local_cache_ttl = 30.seconds
config.cluster.local_cache_max_size = 10_000
config.provider = Session::ClusteredRedisStore(UserSession).new(
client: Redis.new(url: ENV["REDIS_URL"])
)
endprovider = Session.provider
provider.create # New session
provider.delete # Destroy session
provider.regenerate_id # New ID, keep data (post-login security)
provider.valid? # Check validity
provider.data # Access your typed session data
provider.flash # One-request flash messagessession = provider.current_session
session.session_id # Unique ID
session.data # Your SessionData struct
session.valid? # Not expired?
session.expired? # Past expiration?
session.time_until_expiry # Remaining lifetime
session.touch # Extend expiration# Set (available next request)
provider.flash["notice"] = "Saved successfully"
provider.flash["error"] = "Something went wrong"
# Read (clears after access)
provider.flash.now["notice"] # => "Saved successfully"store = Session::MemoryStore(UserSession).new
# Iterate sessions
store.each_session { |s| puts s.data.username }
# Find by criteria
admins = store.find_by { |s| s.data.roles.includes?("admin") }
# Bulk delete (e.g., revoke compromised user)
store.bulk_delete { |s| s.data.user_id == compromised_id }require "http/server"
Session.configure do |config|
config.secret = ENV["SESSION_SECRET"]
config.provider = Session::MemoryStore(UserSession).provider
end
server = HTTP::Server.new([
Session::SessionHandler.new(Session.provider),
YourAppHandler.new,
])
server.listen(8080)The handler automatically loads sessions from cookies, validates bindings, handles corruption gracefully, and sets response cookies.
Session.configure do |config|
# Core
config.secret = ENV["SESSION_SECRET"] # Required
config.timeout = 1.hour # Session lifetime
config.session_key = "_session" # Cookie name
config.sliding_expiration = true # Extend on access
# Security
config.use_kdf = true # PBKDF2 key derivation
config.kdf_iterations = 100_000
config.bind_to_ip = true
config.bind_to_user_agent = true
config.encrypt_redis_data = true
# Performance
config.compress_data = true
config.compression_threshold = 256
# Resilience
config.enable_retry = true
config.circuit_breaker_enabled = true
# Clustering
config.cluster.enabled = true
config.cluster.local_cache_ttl = 30.seconds
config.cluster.local_cache_max_size = 10_000
# Callbacks
config.on_started = ->(id, data) { Log.info { "Session #{id} created" } }
config.on_deleted = ->(id, data) { Log.info { "Session #{id} destroyed" } }
# Metrics
config.metrics_backend = Session::Metrics::LogBackend.new
endFull documentation available at GitBook (or see the docs/ directory).
- Getting Started
- Configuration Guide
- Storage Backends
- Clustering Guide
- Security Best Practices
- AZU Framework Integration
- HTTP::Server Integration
- Fork it
- Create your branch (
git checkout -b feature/awesome) - Write tests
- Make sure
crystal specpasses - Commit and push
- Open a PR
MIT — see LICENSE
Built with Crystal. Maintained by @eliasjpr.