Session Management¶
Cello provides secure cookie-based sessions implemented in Rust. Session data is cryptographically signed to prevent tampering, and cookies are configured with secure defaults (HttpOnly, Secure, SameSite).
Quick Start¶
from cello import App, SessionConfig
app = App()
config = SessionConfig(
secret=b"session-secret-minimum-32-bytes-long",
cookie_name="session_id",
max_age=86400 # 24 hours
)
app.enable_sessions(config)
@app.post("/login")
def login(request):
request.session["user_id"] = "123"
request.session["username"] = "alice"
return {"logged_in": True}
@app.get("/profile")
def profile(request):
user_id = request.session.get("user_id")
if not user_id:
return Response.json({"error": "Not logged in"}, status=401)
return {"user_id": user_id}
SessionConfig¶
config = SessionConfig(
secret=b"session-secret-minimum-32-bytes-long",
cookie_name="session_id",
max_age=86400,
http_only=True,
secure=True,
same_site="Lax"
)
| Parameter | Type | Default | Description |
|---|---|---|---|
secret | bytes | required | Signing secret (minimum 32 bytes) |
cookie_name | str | "session_id" | Name of the session cookie |
max_age | int | 86400 | Session lifetime in seconds (24 hours) |
http_only | bool | True | Prevent JavaScript access to cookie |
secure | bool | True | Send cookie only over HTTPS |
same_site | str | "Lax" | SameSite cookie attribute |
Cookie Security Flags¶
HttpOnly¶
When http_only=True (default), the session cookie is inaccessible to JavaScript:
This prevents XSS attacks from stealing session tokens.
Secure¶
When secure=True (default), the cookie is only sent over HTTPS:
Development Mode
Set secure=False during local development if you are not using HTTPS. Always set it to True in production.
SameSite¶
Controls when the cookie is sent with cross-site requests:
| Value | Behavior |
|---|---|
"Strict" | Cookie only sent for same-site requests |
"Lax" | Cookie sent for same-site requests and top-level navigation (default) |
"None" | Cookie sent for all requests (requires Secure=True) |
# Strict -- maximum security, may break some OAuth flows
config = SessionConfig(
secret=b"secret-key-minimum-32-bytes-long!",
same_site="Strict"
)
# Lax -- good balance of security and usability (default)
config = SessionConfig(
secret=b"secret-key-minimum-32-bytes-long!",
same_site="Lax"
)
Reading and Writing Session Data¶
Setting Values¶
@app.post("/login")
def login(request):
data = request.json()
user = authenticate(data["username"], data["password"])
if user:
request.session["user_id"] = str(user["id"])
request.session["username"] = user["name"]
request.session["role"] = user["role"]
return {"logged_in": True}
return Response.json({"error": "Invalid credentials"}, status=401)
Reading Values¶
@app.get("/dashboard")
def dashboard(request):
user_id = request.session.get("user_id")
username = request.session.get("username")
if not user_id:
return Response.redirect("/login")
return {"user_id": user_id, "username": username}
Deleting Values¶
@app.post("/logout")
def logout(request):
# Clear all session data
request.session.clear()
return {"logged_out": True}
Session Expiration¶
Sessions automatically expire after max_age seconds:
# Session expires after 1 hour
config = SessionConfig(
secret=b"secret-key-minimum-32-bytes-long!",
max_age=3600
)
# Session expires after 7 days
config = SessionConfig(
secret=b"secret-key-minimum-32-bytes-long!",
max_age=604800
)
After expiration, request.session returns an empty dictionary and the client must log in again.
Example: Login/Logout Flow¶
from cello import App, Response, SessionConfig
app = App()
app.enable_sessions(SessionConfig(
secret=b"session-secret-minimum-32-bytes-long",
max_age=86400
))
@app.post("/login")
def login(request):
data = request.json()
if data.get("username") == "admin" and data.get("password") == "secret":
request.session["user_id"] = "1"
request.session["role"] = "admin"
return {"logged_in": True}
return Response.json({"error": "Invalid credentials"}, status=401)
@app.get("/me")
def me(request):
user_id = request.session.get("user_id")
if not user_id:
return Response.json({"error": "Not authenticated"}, status=401)
return {
"user_id": user_id,
"role": request.session.get("role")
}
@app.post("/logout")
def logout(request):
request.session.clear()
return {"logged_out": True}
Sessions vs JWT¶
| Feature | Sessions | JWT |
|---|---|---|
| Storage | Server-side (cookie is just an ID) | Client-side (token contains claims) |
| Stateless | No | Yes |
| Revocation | Immediate (clear session) | Requires blacklist |
| Scalability | Requires shared store for multi-server | No shared state needed |
| Best for | Server-rendered apps, admin panels | APIs, SPAs, mobile apps |
When to Use Sessions
Sessions are best for traditional web applications with server-rendered HTML where you need immediate revocation (logout). For stateless APIs serving SPAs or mobile clients, prefer JWT.
Security Considerations¶
- Use a strong, random secret (minimum 32 bytes)
- Always enable
HttpOnlyto prevent XSS session theft - Enable
Securein production (HTTPS only) - Use
SameSite=LaxorStrictto mitigate CSRF - Set appropriate
max_age-- shorter is more secure - Combine with CSRF protection for form-based applications
- Store secrets in environment variables
Next Steps¶
- CSRF Protection - Protect session-based forms
- Authentication - All authentication methods
- JWT - Stateless token authentication
- Security Headers - Additional browser protections