How RomM Manages Sessions with Redis: Custom ASGI Middleware Deep Dive
RomM stores authenticated user sessions entirely in Redis using a custom RedisSessionMiddleware, attaching session data to the ASGI scope and maintaining a user-specific session set to enable instant bulk revocation across stateless workers.
The RomM application (rommapp/romm) implements a stateless session management layer that leverages Redis as the sole session store. By using a custom ASGI middleware, RomM eliminates the need for server-side in-process session storage, allowing multiple web workers to share session state seamlessly. This article explores how RomM manages sessions with Redis, covering the middleware lifecycle, data structures, and security mechanisms defined in the core source files.
The RedisSessionMiddleware Architecture
The core of RomM's session management resides in backend/handler/auth/middleware/redis_session_middleware.py. This custom ASGI middleware intercepts every incoming request to hydrate session data from Redis and processes outgoing responses to persist changes back to the cache.
Session Lookup and Injection
When a request arrives, the middleware checks for a session cookie (default name defined in backend/handler/auth/constants.py). If present, it extracts the session_id and performs a Redis GET operation on the key session:<session_id>. The JSON-encoded session data is then parsed and injected into the ASGI scope dictionary as scope["session"], making it accessible to downstream FastAPI handlers via the request object.
Response Processing and Persistence
During response generation, the middleware handles three scenarios:
- Session creation: If new session data exists, the middleware generates a UUID, stores JSON-encoded data in Redis with key
session:<session_id>, and sets aSet-Cookieheader containing the generated ID. - Session update: Modified existing sessions are written back to Redis with updated TTL using
ex=SESSION_MAX_AGE_SECONDS. - Session deletion: Empty sessions trigger deletion of the Redis key and immediate cookie clearance.
All Redis operations flow through the shared async_cache instance defined in backend/handler/redis_handler.py, which wraps redis.asyncio.AsyncRedis.
User-Session Sets for Bulk Revocation
Beyond individual session keys, RomM maintains a secondary data structure to track user sessions. For every authenticated user, the middleware creates a Redis set at user_sessions:<user_id> containing all active session_id values for that user.
This design enables user-wide invalidation through the clear_user_sessions(user_id) class method implemented in RedisSessionMiddleware. When invoked, this method retrieves all members from the user set using SMEMBERS, deletes each corresponding session:<session_id> key, and removes the set itself. This proves critical for security operations like password changes or administrative logout commands that require terminating all active sessions simultaneously.
Configuration and Security Attributes
Session behavior is governed by SESSION_MAX_AGE_SECONDS in config.py, which drives both the Redis TTL and cookie Max-Age. The middleware applies secure cookie flags including HttpOnly, SameSite=lax, and optional Secure attributes, ensuring session tokens never leak via XSS or insecure transport.
Because session data lives exclusively in Redis, the application achieves true statelessness—any worker process can handle any request without sticky session requirements, enabling horizontal scaling across multiple RomM instances.
Practical Implementation Examples
Accessing Session Data in FastAPI Endpoints
Downstream handlers retrieve session data via the ASGI scope injected by the middleware:
from fastapi import Request
async def whoami(request: Request):
# The middleware populates request.scope["session"]
session = request.scope.get("session", {})
user_id = session.get("sub") # 'sub' contains the user identifier
return {"user_id": user_id, "session_data": session}
Clearing All Sessions for a User
To revoke every session belonging to a specific user (e.g., after password change):
from handler.auth.middleware.redis_session_middleware import RedisSessionMiddleware
async def logout_user_everywhere(user_id: str):
# Removes all session keys and the user set from Redis
await RedisSessionMiddleware.clear_user_sessions(user_id)
Manual Session Creation
While the middleware handles session lifecycle automatically, manual creation follows this pattern:
import uuid
import json
from handler.redis_handler import async_cache
from config import SESSION_MAX_AGE_SECONDS
async def create_manual_session(user_id: str, extra_data: dict):
session_id = str(uuid.uuid4())
data = {"sub": user_id, **extra_data}
# Store session with TTL
await async_cache.set(
f"session:{session_id}",
json.dumps(data),
ex=SESSION_MAX_AGE_SECONDS,
)
# Track in user set
await async_cache.sadd(f"user_sessions:{user_id}", session_id)
return session_id
Summary
- RomM uses
RedisSessionMiddlewareinbackend/handler/auth/middleware/redis_session_middleware.pyto manage stateless sessions entirely in Redis. - Session data is stored under
session:<session_id>keys with JSON encoding and TTL expiration based onSESSION_MAX_AGE_SECONDS. - User-session sets (
user_sessions:<user_id>) enable bulk revocation via theclear_user_sessionsmethod. - All Redis interactions use the shared
async_cachefrombackend/handler/redis_handler.py. - Secure cookie attributes (
HttpOnly,SameSite=lax) protect the session token in browser storage.
Frequently Asked Questions
How does RomM handle session expiration?
RomM leverages Redis TTL (time-to-live) set via the ex parameter during SET operations. The TTL value equals SESSION_MAX_AGE_SECONDS from config.py, ensuring stale sessions are automatically purged from memory without explicit cleanup logic or background jobs.
Can multiple RomM workers access the same user session?
Yes. Because sessions reside entirely in Redis rather than local memory, any worker process can retrieve session data using the session_id from the cookie. This eliminates the need for sticky sessions or shared memory between workers, supporting horizontal scaling.
What happens when a user logs out?
The middleware detects empty session data and removes the Redis key session:<session_id>. Additionally, calling RedisSessionMiddleware.clear_user_sessions(user_id) removes all active sessions for that user by iterating through their user_sessions:<user_id> set and deleting each corresponding session key.
Where is the session cookie name configured?
The cookie name and related constants are defined in backend/handler/auth/constants.py, with the default value typically set to session. The middleware references these constants when reading request cookies and setting Set-Cookie headers in responses.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →