Skip to main content

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:

  1. Opening the Session: When a RequestContext is pushed (via RequestContext.push()), it calls session_interface.open_session(app, request). This happens before URL matching occurs, allowing the session to be available even in custom URL converters.
  2. Saving the Session: At the end of the request, during Flask.process_response(), the application calls session_interface.save_session(app, session, response). This is where the session data is serialized and sent back to the client (e.g., via a Set-Cookie header).

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 _permanent key in the session dictionary. When True, the session persists beyond the browser's lifetime based on app.permanent_session_lifetime.
  • modified: A boolean flag indicating if the session data has changed. The default SecureCookieSession uses a CallbackDict to automatically set this to True when keys are updated.
  • accessed: Automatically set to True whenever the session is accessed via the session proxy in RequestContext.
  • 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.

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 ...

The SessionInterface provides several helper methods that pull from the application's configuration to determine cookie behavior. These include:

  • get_cookie_domain(app): Uses SESSION_COOKIE_DOMAIN.
  • get_cookie_path(app): Uses SESSION_COOKIE_PATH or falls back to APPLICATION_ROOT.
  • get_cookie_httponly(app): Uses SESSION_COOKIE_HTTPONLY.
  • get_cookie_secure(app): Uses SESSION_COOKIE_SECURE.
  • get_cookie_samesite(app): Uses SESSION_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