Skip to main content

Implementing Custom Session Backends

To implement a custom session backend in this project, you must subclass SessionInterface and provide a session object that implements SessionMixin. This allows you to store session data in external stores like Redis, a database, or a filesystem instead of the default signed cookie.

Implementing a Custom Session Interface

The following example demonstrates a custom session interface that uses a hypothetical external store. It uses a session ID stored in a cookie to retrieve and persist data.

import uuid
from flask import Flask, session
from flask.sessions import SessionInterface, SessionMixin
from flask.globals import app_ctx

class MySession(dict, SessionMixin):
"""A custom session object that tracks its own state."""
def __init__(self, initial=None, sid=None, new=False):
super().__init__(initial or {})
self.sid = sid
self.new = new

class MySessionInterface(SessionInterface):
def open_session(self, app: Flask, request):
# 1. Retrieve the session ID from the cookie
sid = request.cookies.get(self.get_cookie_name(app))

if not sid:
# Create a new session if no ID exists
return MySession(sid=str(uuid.uuid4()), new=True)

# 2. Load data from your backend (e.g., Redis/DB)
# val = backend.get(sid)
val = {"user_id": 1} # Mocked data

return MySession(val, sid=sid)

def save_session(self, app: Flask, session: MySession, response):
# 3. Determine if the cookie needs to be set
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)

if not session:
# Handle session deletion
if session.modified:
response.delete_cookie(
self.get_cookie_name(app),
domain=domain,
path=path
)
return

# 4. Persist data to your backend
# backend.set(session.sid, dict(session))

# 5. Set the cookie on the response if modified or permanent
if not self.should_set_cookie(app, session):
return

httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(app)
expires = self.get_expiration_time(app, session)

response.set_cookie(
self.get_cookie_name(app),
session.sid,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite
)

# Register the interface
app = Flask(__name__)
app.session_interface = MySessionInterface()

Key Components

SessionMixin

Your session object must inherit from SessionMixin (found in src/flask/sessions.py). This mixin provides the permanent property and tracks the modified and accessed flags. Flask uses these flags to determine if a Vary: Cookie header should be added or if the save_session logic should update the cookie.

open_session

This method is called at the start of every request. It must return an object that behaves like a dictionary and implements SessionMixin. If it returns None, Flask falls back to a NullSession, which prevents modifications if a secret key is missing.

save_session

This method is called at the end of the request. You are responsible for:

  1. Checking session.modified or session.permanent via self.should_set_cookie(app, session).
  2. Persisting the dictionary data to your storage.
  3. Updating the client's cookie using response.set_cookie.

Advanced Patterns

Accessing Request Data Early

By default, open_session is called before URL matching occurs. If your session logic depends on the matched route (e.g., request.endpoint), you must manually trigger matching using app_ctx.match_request() as seen in tests/test_session_interface.py:

from flask.globals import app_ctx

class MySessionInterface(SessionInterface):
def open_session(self, app, request):
# Manually match the request to populate request.endpoint
app_ctx.match_request()
if request.endpoint == "sensitive_route":
# Custom logic for specific endpoints
pass
return MySession()

You can override helper methods like get_cookie_name to change behavior based on the current request. This pattern is used in tests/test_reqctx.py to vary the cookie name by URL:

import flask
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)

Troubleshooting

  • NullSession Errors: If open_session returns None, Flask creates a NullSession. Any attempt to modify a NullSession will raise a RuntimeError (usually complaining about a missing SECRET_KEY). Ensure your open_session always returns a valid SessionMixin object if you want to support sessions.
  • Concurrency: As noted in src/flask/sessions.py, multiple requests with the same session may be handled concurrently. If your backend (like a database) does not handle atomic updates, you may need to implement synchronization logic within save_session.
  • Vary Header: If session.accessed is True, Flask will automatically add Vary: Cookie to the response headers. This is handled by the request context and ensures caches don't serve session-specific content to the wrong users.