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