Session Management
Session management is a critical component of web applications, allowing you to store and retrieve user data across multiple requests. Nexios provides a robust, flexible session management system that's easy to configure yet powerful enough for complex applications.
Basic Session Setup
Setting up sessions in your Nexios application is straightforward:
from nexios import NexiosApp
from nexios.session.middleware import SessionMiddleware
app = NexiosApp()
# Required: Configure a secret key for signing sessions
app.config.secret_key = "your-secure-secret-key"
# Add the session middleware
app.add_middleware(SessionMiddleware)
With this minimal setup, Nexios will use the default cookie-based session backend. Your routes can now access the session through the request object:
@app.get("/")
async def index(req, res):
# Access the session
counter = req.session.get("counter", 0)
counter += 1
req.session["counter"] = counter
return res.text(f"You've visited this page {counter} times")
Session Configuration Options
Nexios offers various configuration options for customizing session behavior:
from nexios import NexiosApp
from nexios.session.middleware import SessionMiddleware
from nexios.session.file import FileSessionInterface
app = NexiosApp()
# Required secret key for secure cookies
app.config.secret_key = "your-secure-secret-key"
# Session configuration
app.config.session = {
# Session cookie name
"session_cookie_name": "nexios_session",
# Cookie settings
"cookie_path": "/",
"cookie_domain": None,
"cookie_secure": True,
"cookie_httponly": True,
"cookie_samesite": "lax",
# Session expiration (in seconds)
"expiry": 86400, # 24 hours
# Session backend
"manager": FileSessionInterface,
# Backend-specific settings
"directory": "sessions",
"prefix": "session_"
}
# Add the session middleware
app.add_middleware(SessionMiddleware())
Configuration Options Reference
Option | Description | Default |
---|---|---|
session_cookie_name | Name of the cookie storing the session ID | "session_id" |
cookie_path | Path for which the cookie is valid | "/" |
cookie_domain | Domain for which the cookie is valid | None |
cookie_secure | Whether cookie should only be sent over HTTPS | False |
cookie_httponly | Whether cookie should be accessible via JavaScript | True |
cookie_samesite | SameSite attribute ("lax" , "strict" , or "none" ) | "lax" |
expiry | Session lifetime in seconds | 86400 (24 hours) |
manager | Session backend class | SignedSessionManager |
Basic Session Operations
@app.get("/session-demo")
async def session_demo(req, res):
# Get a value with default if not present
user_id = req.session.get("user_id", None)
# Set a value
req.session["last_visit"] = time.time()
# Check if a key exists
if "preferences" in req.session:
preferences = req.session["preferences"]
# Remove a key
if "temporary_data" in req.session:
del req.session["temporary_data"]
# Clear the entire session
# req.session.clear()
return res.json({
"user_id": user_id,
"session_keys": list(req.session.keys())
})
Session Properties and Methods
Sessions in Nexios behave similar to dictionaries but with additional methods:
Method/Property | Description |
---|---|
session.get(key, default=None) | Get a value, returning default if not present |
session[key] = value | Set a session value |
key in session | Check if key exists in the session |
del session[key] | Delete a key from the session |
session.clear() | Remove all keys from the session |
session.keys() | Get all keys in the session |
session.items() | Get all key-value pairs in the session |
session.pop(key, default=None) | Get and remove a key, returning default if not present |
session.is_empty() | Check if session has no data |
session.modified | Whether session has been modified |
Session Expiration
By default, sessions expire after 24 hours (86400 seconds). You can customize this:
# Set global session expiration time
app.config.session = {
"expiry": 3600 # 1 hour
}
# Or set per-session expiration time
@app.post("/login")
async def login(req, res):
# Authenticate user...
req.session["user_id"] = user.id
# Set this specific session to expire in 30 minutes
req.session.set_expiry(1800)
return res.json({"success": True})
Session Backends
Nexios supports multiple session backends to store session data. Each backend has different characteristics suitable for various use cases.
Signed Cookie Sessions (Default)
The simplest session backend, storing the session data directly in a signed cookie:
from nexios.session.signed_cookies import SignedSessionManager
app.config.session = {
"manager": SignedSessionManager
}
Pros:
- No server-side storage required
- Works well in distributed environments
- Simple setup
Cons:
- Limited storage size (4KB cookie limit)
- Session data sent with every request
- Cannot be invalidated server-side
File-based Sessions
Stores session data in files on the server filesystem:
from nexios.session.file import FileSessionInterface
app.config.session = {
"manager": FileSessionInterface,
"directory": "sessions", # Directory to store session files
"prefix": "session_" # Prefix for session files
}
Pros:
- Unlimited session data size
- Sessions can be invalidated server-side
- Simple setup for single-server environments
Cons:
- Not suitable for distributed environments
- Requires filesystem access
- Needs cleanup of expired session files
Building Custom Session Backends
You can create custom session backends by implementing the BaseSessionInterface
:
from nexios.session.base import BaseSessionInterface
class RedisSessionInterface(BaseSessionInterface):
"""Redis-backed session interface"""
def __init__(self, session_key=None):
super().__init__(session_key)
self.redis_client = redis.Redis()
async def load(self):
"""Load the session data from Redis"""
if not self.session_key:
return
data = self.redis_client.get(f"session:{self.session_key}")
if data:
self._data = json.loads(data)
async def save(self):
"""Save the session data to Redis"""
if not self.session_key:
self.session_key = self.generate_sid()
expiry = self.get_expiry_age()
self.redis_client.setex(
f"session:{self.session_key}",
expiry,
json.dumps(self._data)
)
self.modified = False
def get_session_key(self):
"""Return the session key"""
return self.session_key
Session Security Best Practices
Session management requires careful attention to security:
Generate a Strong Secret Key
import secrets
# Generate a secure random key
app.config.secret_key = secrets.token_hex(32)
# For production, store this in environment variables
app.config.secret_key = os.environ.get("SECRET_KEY")
Enable Secure Cookies
app.config.session = {
"cookie_secure": True, # Only send cookies over HTTPS
"cookie_httponly": True, # Prevent JavaScript access
"cookie_samesite": "lax" # Mitigate CSRF attacks
}
Use Appropriate Session Expiration
# Short expiration for sensitive operations
@app.post("/banking/transfer")
async def transfer(req, res):
# Verify authentication is recent
auth_time = req.session.get("auth_time", 0)
if time.time() - auth_time > 300: # 5 minutes
return res.redirect("/re-authenticate")
# Process transfer...
Implement Session Invalidation
@app.post("/logout")
async def logout(req, res):
# Clear session and remove cookie
req.session.clear()
return res.redirect("/login")
Practical Examples
Example 1: User Authentication Flow
@app.post("/login")
async def login(req, res):
data = await req.form
username = data.get("username")
password = data.get("password")
# Authenticate user (pseudo-code)
user = authenticate_user(username, password)
if not user:
return res.redirect("/login?error=invalid_credentials")
# Store user info in session
req.session["user_id"] = user.id
req.session["username"] = user.username
req.session["auth_time"] = time.time()
req.session["is_admin"] = user.is_admin
return res.redirect("/dashboard")
@app.get("/dashboard")
async def dashboard(req, res):
# Check if user is logged in
if "user_id" not in req.session:
return res.redirect("/login")
username = req.session["username"]
return res.html(f"<h1>Welcome, {username}!</h1>")
@app.post("/logout")
async def logout(req, res):
req.session.clear()
return res.redirect("/login?message=logged_out")
Example 2: Shopping Cart
@app.get("/cart")
async def view_cart(req, res):
# Initialize cart if it doesn't exist
cart = req.session.get("cart", {})
# Calculate total
total = sum(item["price"] * item["quantity"] for item in cart.values())
return res.json({
"items": cart,
"total": total
})
@app.post("/cart/add/{product_id}")
async def add_to_cart(req, res):
product_id = req.path_params.product_id
quantity = int(req.query_params.get("quantity", 1))
# Get product details (pseudo-code)
product = get_product(product_id)
if not product:
return res.json({"error": "Product not found"}, status_code=404)
# Get or initialize cart
cart = req.session.get("cart", {})
# Add or update product in cart
if product_id in cart:
cart[product_id]["quantity"] += quantity
else:
cart[product_id] = {
"name": product.name,
"price": product.price,
"quantity": quantity
}
# Save cart to session
req.session["cart"] = cart
return res.json({"success": True, "cart": cart})
@app.post("/cart/clear")
async def clear_cart(req, res):
if "cart" in req.session:
del req.session["cart"]
return res.json({"success": True})
Example 3: Multi-step Form with Session Data
@app.get("/wizard/step1")
async def wizard_step1(req, res):
# Initialize or get form data
form_data = req.session.get("wizard_data", {})
return res.html_template("wizard/step1.html", form_data=form_data)
@app.post("/wizard/step1")
async def wizard_step1_post(req, res):
form_data = await req.form
# Validate form (pseudo-code)
if not validate_step1(form_data):
return res.redirect("/wizard/step1?error=invalid_data")
# Initialize wizard data if not exists
wizard_data = req.session.get("wizard_data", {})
# Update with step 1 data
wizard_data.update({
"name": form_data.get("name"),
"email": form_data.get("email")
})
# Save back to session
req.session["wizard_data"] = wizard_data
# Proceed to next step
return res.redirect("/wizard/step2")
@app.post("/wizard/complete")
async def wizard_complete(req, res):
# Get all wizard data
wizard_data = req.session.get("wizard_data", {})
# Process the complete submission
result = process_wizard_submission(wizard_data)
# Clear wizard data from session
del req.session["wizard_data"]
return res.redirect(f"/wizard/success?id={result.id}")