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.