The Session Interface Architecture
The session interface architecture in Flask provides a pluggable system for managing user data across multiple requests. At its core, this system is defined by the SessionInterface class in src/flask/sessions.py, which abstracts the logic for loading and persisting session data.
The Session Lifecycle
Flask manages sessions as part of the request context lifecycle. The interaction between the application and the session interface occurs at two primary points:
- Opening the Session: When a request context is pushed (via
AppContext.pushinsrc/flask/ctx.py), Flask callssession_interface.open_session. This happens before request matching, ensuring that session data is available even for custom URL converters. - Saving the Session: At the end of the request, within
Flask.process_response(insrc/flask/app.py), Flask callssession_interface.save_session. This occurs after allafter_requestfunctions have executed, allowing the session to capture any changes made during the response phase.
If open_session returns None, Flask falls back to session_interface.make_null_session, which creates a NullSession. This object allows read access but raises a RuntimeError if a modification is attempted without a configured SECRET_KEY.
Core Components
SessionInterface
The SessionInterface is the base class that defines the contract for session backends. While it provides default implementations for cookie configuration (like get_cookie_name, get_cookie_domain, and get_cookie_path), subclasses must implement:
open_session(app, request): Loads the session from the request.save_session(app, session, response): Persists the session to the response.
SessionMixin and State Tracking
Sessions in Flask are more than just dictionaries; they implement the SessionMixin (found in src/flask/sessions.py). This mixin adds three critical flags:
permanent: Indicates if the session should persist beyond the browser closing.modified: Tracks if the session's content has changed.accessed: Tracks if the session was read during the request.
The default SecureCookieSession uses a werkzeug.datastructures.CallbackDict to automatically set self.modified = True when top-level keys are changed.
SecureCookieSessionInterface
This is the default implementation used by Flask. It stores session data in a cryptographically signed cookie on the client side. It uses the itsdangerous.URLSafeTimedSerializer to ensure that the session data cannot be tampered with by the client.
# Example of how SecureCookieSessionInterface uses itsdangerous
def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None:
if not app.secret_key:
return None
return URLSafeTimedSerializer(
app.secret_key,
salt=self.salt,
serializer=self.serializer,
signer_kwargs={
"key_derivation": self.key_derivation,
"digest_method": self.digest_method,
},
)
Session State and HTTP Headers
The SessionInterface architecture directly influences HTTP headers through the accessed and modified flags:
- Vary Header: In
save_session, ifsession.accessedisTrue, Flask addsVary: Cookieto the response. This informs caching proxies that the response may differ based on the user's session. - Set-Cookie Header: The
should_set_cookiemethod determines if aSet-Cookieheader is necessary. By default, this returnsTrueifsession.modifiedisTrueor if the session ispermanentandSESSION_REFRESH_EACH_REQUESTis enabled.
Implementing a Custom Session Interface
To implement a custom session backend (e.g., using Redis or a database), you subclass SessionInterface and assign it to app.session_interface.
A common pattern for custom interfaces is to handle request matching manually if the session logic depends on the matched endpoint. As seen in tests/test_session_interface.py:
class MySessionInterface(SessionInterface):
def open_session(self, app, request):
# Manually trigger matching so request.endpoint is available
# during session opening.
ctx = _cv_app.get(None)
if ctx is not None:
ctx.match_request()
# Load session logic here...
return MySession(data)
def save_session(self, app, session, response):
# Persistence logic here...
pass
Another use case is dynamic cookie configuration. By overriding get_cookie_name, you can change the session cookie based on the request URL:
class PathAwareSessionInterface(SecureCookieSessionInterface):
def get_cookie_name(self, app):
if flask.request.path.startswith("/api"):
return "api_session"
return super().get_cookie_name(app)
Important Considerations
- Nested Modifications: Flask's automatic modification tracking only detects changes to the top-level dictionary. If you modify a nested object (e.g.,
session['user']['name'] = 'new'), you must manually setsession.modified = True. - Null Sessions: If
open_sessionfails or is skipped, the resultingNullSessionwill prevent accidental writes to a non-existent session, providing a helpful error message about the missingSECRET_KEY. - Concurrency: Flask does not provide built-in synchronization for session access. If multiple concurrent requests modify the same session, the last one to save will overwrite previous changes.