Skip to main content

The Default Secure Cookie Implementation

When you attempt to store data in flask.session without configuring a SECRET_KEY, Flask raises a RuntimeError via the NullSession class. This is because Flask's default session implementation, SecureCookieSessionInterface, requires a cryptographic key to sign session data, ensuring that users cannot tamper with their session state.

The Session Object

The SecureCookieSession class is the default object used for flask.session. It behaves like a standard Python dictionary but includes logic to track whether it has been changed during a request.

from flask import session

@app.route("/set")
def set_session():
session["user_id"] = 42 # Automatically sets session.modified = True
return "Session set"

Internally, SecureCookieSession inherits from werkzeug.datastructures.CallbackDict. It uses an on_update callback to set its modified attribute to True whenever a key is set or deleted.

The Nested Object Gotcha

Because Flask only tracks top-level changes to the session dictionary, modifying a mutable object stored inside the session will not automatically trigger a save.

@app.route("/update_prefs")
def update_prefs():
# This modification is NOT detected by CallbackDict
session["prefs"]["theme"] = "dark"

# You must manually flag the session as modified
session.modified = True
return "Preferences updated"

Security through Signing

Flask uses the itsdangerous library to sign session cookies. The SecureCookieSessionInterface.get_signing_serializer method creates a URLSafeTimedSerializer using your application's SECRET_KEY.

Important: Session cookies in Flask are signed, not encrypted. This means:

  1. Integrity: Users cannot modify the session data (e.g., changing user_id from 42 to 1) because the cryptographic signature would become invalid.
  2. Visibility: Users can read the contents of the session cookie by decoding the base64-encoded payload. Never store sensitive information like passwords or API keys in the session.

Key Rotation with Fallbacks

If you need to change your SECRET_KEY without invalidating all existing user sessions, you can use SECRET_KEY_FALLBACKS. When SecureCookieSessionInterface.open_session attempts to load a cookie, it uses the get_signing_serializer to check the current key and then any fallback keys provided in the configuration.

app.config.update(
SECRET_KEY="new-very-secret-key",
SECRET_KEY_FALLBACKS=["old-secret-key", "even-older-key"]
)

In SecureCookieSessionInterface.get_signing_serializer, Flask ensures the current secret_key is at the top of the list because itsdangerous uses the first key for signing new cookies while using the rest for verifying existing ones.

Serialization of Complex Types

Flask uses a specialized TaggedJSONSerializer (aliased as session_json_serializer in flask.sessions) to convert session data to a string. Unlike standard JSON, this serializer supports several Python-specific types:

  • datetime objects
  • UUID objects
  • tuple (distinguished from lists)
  • bytes
  • Markup strings (from MarkupSafe)

This allows you to store objects like a timezone-aware datetime directly in the session:

from datetime import datetime, timezone

@app.route("/stamp")
def stamp():
session["last_login"] = datetime.now(timezone.utc)
return "Timestamped"

Session Lifecycle

The SecureCookieSessionInterface manages the session through two primary methods:

  1. open_session(app, request): Called at the start of a request. It retrieves the cookie named by SESSION_COOKIE_NAME, verifies the signature using itsdangerous, and returns a SecureCookieSession object. If the signature is invalid (e.g., the key changed or the cookie was tampered with), it returns a fresh, empty session.
  2. save_session(app, session, response): Called at the end of a request. It checks session.should_set_cookie (which looks at session.modified). If modified, it serializes the data, signs it, and adds a Set-Cookie header to the response.

Configuration Reference

You can customize the behavior of the session cookie using the following configuration variables in your Flask app:

Config KeyDefaultDescription
SESSION_COOKIE_NAME"session"The name of the cookie stored on the client.
SESSION_COOKIE_HTTPONLYTruePrevents JavaScript from accessing the cookie.
SESSION_COOKIE_SECUREFalseIf True, the cookie is only sent over HTTPS.
SESSION_COOKIE_SAMESITE"Lax"Controls CSRF protection via the SameSite attribute.
PERMANENT_SESSION_LIFETIME31 daysHow long a "permanent" session lasts.

To make a session persist after the browser closes, set session.permanent = True. This causes get_expiration_time to return a timestamp based on PERMANENT_SESSION_LIFETIME instead of creating a "session-only" cookie.