Skip to main content

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:

  1. Opening the Session: When a request context is pushed (via AppContext.push in src/flask/ctx.py), Flask calls session_interface.open_session. This happens before request matching, ensuring that session data is available even for custom URL converters.
  2. Saving the Session: At the end of the request, within Flask.process_response (in src/flask/app.py), Flask calls session_interface.save_session. This occurs after all after_request functions 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, if session.accessed is True, Flask adds Vary: Cookie to the response. This informs caching proxies that the response may differ based on the user's session.
  • Set-Cookie Header: The should_set_cookie method determines if a Set-Cookie header is necessary. By default, this returns True if session.modified is True or if the session is permanent and SESSION_REFRESH_EACH_REQUEST is 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 set session.modified = True.
  • Null Sessions: If open_session fails or is skipped, the resulting NullSession will prevent accidental writes to a non-existent session, providing a helpful error message about the missing SECRET_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.