The Session System Architecture
The session system in this codebase is built around a pluggable architecture that decouples session storage logic from the request lifecycle. This is achieved through two primary components: the SessionInterface, which defines how sessions are loaded and persisted, and the SessionMixin, which provides the necessary metadata tracking for session objects.
The Session Lifecycle
Flask manages sessions as part of the request context. The lifecycle is split into two distinct phases:
- Opening the Session: When a
RequestContextis pushed (viaRequestContext.push()), it callssession_interface.open_session(app, request). This happens before URL matching occurs, allowing the session to be available even in custom URL converters. - Saving the Session: At the end of the request, during
Flask.process_response(), the application callssession_interface.save_session(app, session, response). This is where the session data is serialized and sent back to the client (e.g., via aSet-Cookieheader).
If open_session returns None, Flask falls back to session_interface.make_null_session(app), which creates a NullSession. This object allows read access but raises a RuntimeError if any modifications are attempted, typically indicating a missing SECRET_KEY.
Session State Tracking with SessionMixin
Any object used as a session must implement the SessionMixin. In this codebase, sessions are typically dictionary-like objects (subclassing dict and MutableMapping) that use the mixin to track their state:
permanent: Reflects the_permanentkey in the session dictionary. WhenTrue, the session persists beyond the browser's lifetime based onapp.permanent_session_lifetime.modified: A boolean flag indicating if the session data has changed. The defaultSecureCookieSessionuses aCallbackDictto automatically set this toTruewhen keys are updated.accessed: Automatically set toTruewhenever the session is accessed via thesessionproxy inRequestContext.new: Indicates if the session was created during the current request.
The "Accessed" Flag and Caching
The accessed flag plays a critical role in HTTP caching. When session.accessed is True, Flask ensures that a Vary: Cookie header is added to the response. This prevents intermediate caches from serving session-specific content to the wrong users.
# From src/flask/ctx.py
@property
def session(self) -> SessionMixin:
"""Accessing this sets :attr:`.SessionMixin.accessed`."""
session = self._get_session()
session.accessed = True
return session
Implementing a Custom Session Interface
To replace the default cookie-based session management, you must implement a subclass of SessionInterface. The core responsibility is to provide implementations for open_session and save_session.
Example: Dynamic Cookie Names
The following example, adapted from tests/test_reqctx.py, demonstrates how to extend the default interface to change the cookie name based on the request URL:
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)
app = Flask(__name__)
app.session_interface = PathAwareSessionInterface()
Handling Request Matching
Because sessions are opened before the request is matched to a URL rule, request.endpoint is initially None. If your open_session logic depends on the matched endpoint, you must manually trigger matching:
# From tests/test_session_interface.py
class MySessionInterface(SessionInterface):
def open_session(self, app, request):
# Manually trigger matching if endpoint info is needed
from flask.globals import app_ctx
app_ctx.match_request()
assert request.endpoint is not None
# ... load session logic ...
Configuration and Cookie Behavior
The SessionInterface provides several helper methods that pull from the application's configuration to determine cookie behavior. These include:
get_cookie_domain(app): UsesSESSION_COOKIE_DOMAIN.get_cookie_path(app): UsesSESSION_COOKIE_PATHor falls back toAPPLICATION_ROOT.get_cookie_httponly(app): UsesSESSION_COOKIE_HTTPONLY.get_cookie_secure(app): UsesSESSION_COOKIE_SECURE.get_cookie_samesite(app): UsesSESSION_COOKIE_SAMESITE.
The decision to actually send a Set-Cookie header is governed by should_set_cookie(app, session). It returns True if the session has been modified, or if the session is permanent and SESSION_REFRESH_EACH_REQUEST is enabled.
Modification Gotchas
While the default session implementation tracks modifications to the top-level dictionary, it cannot detect changes to nested mutable objects (like lists or dictionaries) stored within the session. In such cases, you must manually set the flag:
session["my_list"].append(42)
session.modified = True # Required for the change to be saved