Implementing Custom Session Backends
To implement a custom session backend in this project, you must subclass SessionInterface and provide a session object that implements SessionMixin. This allows you to store session data in external stores like Redis, a database, or a filesystem instead of the default signed cookie.
Implementing a Custom Session Interface
The following example demonstrates a custom session interface that uses a hypothetical external store. It uses a session ID stored in a cookie to retrieve and persist data.
import uuid
from flask import Flask, session
from flask.sessions import SessionInterface, SessionMixin
from flask.globals import app_ctx
class MySession(dict, SessionMixin):
"""A custom session object that tracks its own state."""
def __init__(self, initial=None, sid=None, new=False):
super().__init__(initial or {})
self.sid = sid
self.new = new
class MySessionInterface(SessionInterface):
def open_session(self, app: Flask, request):
# 1. Retrieve the session ID from the cookie
sid = request.cookies.get(self.get_cookie_name(app))
if not sid:
# Create a new session if no ID exists
return MySession(sid=str(uuid.uuid4()), new=True)
# 2. Load data from your backend (e.g., Redis/DB)
# val = backend.get(sid)
val = {"user_id": 1} # Mocked data
return MySession(val, sid=sid)
def save_session(self, app: Flask, session: MySession, response):
# 3. Determine if the cookie needs to be set
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
if not session:
# Handle session deletion
if session.modified:
response.delete_cookie(
self.get_cookie_name(app),
domain=domain,
path=path
)
return
# 4. Persist data to your backend
# backend.set(session.sid, dict(session))
# 5. Set the cookie on the response if modified or permanent
if not self.should_set_cookie(app, session):
return
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session)
response.set_cookie(
self.get_cookie_name(app),
session.sid,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite
)
# Register the interface
app = Flask(__name__)
app.session_interface = MySessionInterface()
Key Components
SessionMixin
Your session object must inherit from SessionMixin (found in src/flask/sessions.py). This mixin provides the permanent property and tracks the modified and accessed flags. Flask uses these flags to determine if a Vary: Cookie header should be added or if the save_session logic should update the cookie.
open_session
This method is called at the start of every request. It must return an object that behaves like a dictionary and implements SessionMixin. If it returns None, Flask falls back to a NullSession, which prevents modifications if a secret key is missing.
save_session
This method is called at the end of the request. You are responsible for:
- Checking
session.modifiedorsession.permanentviaself.should_set_cookie(app, session). - Persisting the dictionary data to your storage.
- Updating the client's cookie using
response.set_cookie.
Advanced Patterns
Accessing Request Data Early
By default, open_session is called before URL matching occurs. If your session logic depends on the matched route (e.g., request.endpoint), you must manually trigger matching using app_ctx.match_request() as seen in tests/test_session_interface.py:
from flask.globals import app_ctx
class MySessionInterface(SessionInterface):
def open_session(self, app, request):
# Manually match the request to populate request.endpoint
app_ctx.match_request()
if request.endpoint == "sensitive_route":
# Custom logic for specific endpoints
pass
return MySession()
Dynamic Cookie Configuration
You can override helper methods like get_cookie_name to change behavior based on the current request. This pattern is used in tests/test_reqctx.py to vary the cookie name by URL:
import flask
from flask.sessions import SecureCookieSessionInterface
class PathAwareSessionInterface(SecureCookieSessionInterface):
def get_cookie_name(self, app):
if flask.request.url.endswith("dynamic_cookie"):
return "dynamic_cookie_name"
return super().get_cookie_name(app)
Troubleshooting
- NullSession Errors: If
open_sessionreturnsNone, Flask creates aNullSession. Any attempt to modify aNullSessionwill raise aRuntimeError(usually complaining about a missingSECRET_KEY). Ensure youropen_sessionalways returns a validSessionMixinobject if you want to support sessions. - Concurrency: As noted in
src/flask/sessions.py, multiple requests with the same session may be handled concurrently. If your backend (like a database) does not handle atomic updates, you may need to implement synchronization logic withinsave_session. - Vary Header: If
session.accessedisTrue, Flask will automatically addVary: Cookieto the response headers. This is handled by the request context and ensures caches don't serve session-specific content to the wrong users.