Creating a Custom Session Backend
Flask uses the SessionInterface to manage how session data is loaded and saved. By default, Flask uses SecureCookieSessionInterface, which stores session data in a signed cookie on the client's browser. However, you can implement a custom backend to store session data in an external database, Redis, or the filesystem.
This tutorial walks through creating a custom session backend that stores data in a server-side dictionary, using a unique session ID stored in a cookie.
Prerequisites
To follow this tutorial, you should have a basic Flask application structure. You will be working with the following classes from flask.sessions:
SessionInterface: The base class for session implementations.SessionMixin: A mixin that adds session-specific attributes to a dictionary-like object.
Step 1: Define the Session Object
The session object must behave like a dictionary and implement the SessionMixin interface. The mixin provides properties like permanent, new, modified, and accessed.
We use werkzeug.datastructures.CallbackDict to automatically set the modified flag whenever the dictionary is updated.
import uuid
from flask.sessions import SessionMixin
from werkzeug.datastructures import CallbackDict
class MySession(CallbackDict, SessionMixin):
def __init__(self, initial=None, sid=None, new=False):
def on_update(self):
self.modified = True
super().__init__(initial, on_update)
self.sid = sid
self.new = new
self.modified = False
In this implementation:
sidstores the unique session identifier.newindicates if the session was just created for this request.on_updateensures thatsession.modifiedbecomesTrueas soon as you set a value (e.g.,session["user_id"] = 1).
Step 2: Implement the Session Interface
The SessionInterface requires two primary methods: open_session and save_session.
from flask.sessions import SessionInterface
class MySessionInterface(SessionInterface):
def __init__(self):
# In a real app, this would be a Redis client or database connection
self.store = {}
def open_session(self, app, request):
# 1. Get the session ID from the cookie
cookie_name = self.get_cookie_name(app)
sid = request.cookies.get(cookie_name)
# 2. If no ID, create a new session
if not sid:
return MySession(sid=str(uuid.uuid4()), new=True)
# 3. Load data from the store
data = self.store.get(sid)
if data is not None:
return MySession(data, sid=sid)
# 4. Fallback to a new session if ID is invalid/expired in store
return MySession(sid=str(uuid.uuid4()), new=True)
def save_session(self, app, session, response):
# 1. Get cookie configuration using helper methods
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
name = self.get_cookie_name(app)
# 2. Add Vary: Cookie header if the session was accessed
if session.accessed:
response.vary.add("Cookie")
# 3. Handle session deletion
if not session:
if session.modified:
self.store.pop(session.sid, None)
response.delete_cookie(
name, domain=domain, path=path
)
return
# 4. Check if the cookie needs to be updated
if not self.should_set_cookie(app, session):
return
# 5. Save to store and set the cookie
self.store[session.sid] = dict(session)
expires = self.get_expiration_time(app, session)
response.set_cookie(
name,
session.sid,
expires=expires,
httponly=self.get_cookie_httponly(app),
domain=domain,
path=path,
secure=self.get_cookie_secure(app),
samesite=self.get_cookie_samesite(app)
)
Step 3: Use Helper Methods for Consistency
When implementing save_session, always use the helper methods provided by SessionInterface (like get_cookie_domain and get_cookie_httponly). These methods automatically pull values from the Flask application's configuration (e.g., SESSION_COOKIE_DOMAIN, SESSION_COOKIE_HTTPONLY), ensuring your custom backend respects standard Flask settings.
The should_set_cookie method is particularly important. It checks session.modified and app.config["SESSION_REFRESH_EACH_REQUEST"] to determine if a Set-Cookie header is actually necessary, which helps reduce response size.
Step 4: Register the Custom Interface
To activate your custom backend, assign an instance of your interface to app.session_interface.
from flask import Flask, session
app = Flask(__name__)
app.secret_key = "overridden-but-still-good-practice"
app.session_interface = MySessionInterface()
@app.route("/set/<name>")
def set_name(name):
session["name"] = name
return f"Stored {name}"
@app.route("/get")
def get_name():
return f"Hello, {session.get('name', 'Stranger')}"
Advanced: Accessing Request Data in open_session
By default, open_session is called before Flask matches the request to a URL rule. If your session logic depends on the matched endpoint (e.g., different session timeouts for different parts of the app), you must manually trigger request matching.
You can do this by importing app_ctx from flask.globals and calling match_request():
from flask.globals import app_ctx
def open_session(self, app, request):
# Manually match the request to populate request.endpoint
app_ctx.match_request()
if request.endpoint == "sensitive_area":
# Custom logic for specific endpoints
pass
# ... rest of open_session logic
Handling Configuration Errors
If your open_session implementation encounters a configuration error (like a missing database connection) and cannot return a valid session, it should return None. Flask will then call make_null_session(app), which returns a NullSession.
A NullSession allows the application to continue running but will raise a RuntimeError if the code attempts to modify the session, providing a clear error message to the developer.