Skip to main content

Using Signed Cookie Sessions

Flask uses signed cookies for session management by default. This implementation relies on the SecureCookieSessionInterface to handle the serialization and signing of session data, and the SecureCookieSession class to track the state of the session during a request.

The Session Object

The SecureCookieSession class (found in src/flask/sessions.py) is the object you interact with when using flask.session. It inherits from werkzeug.datastructures.CallbackDict and SessionMixin.

State Tracking

The session object tracks two primary states: modified and accessed.

  • accessed: This flag is set to True whenever a key is read from the session. When accessed is true, Flask automatically adds a Vary: Cookie header to the response. This informs caching proxies that the response content may vary based on the user's session cookie.
  • modified: This flag is set to True whenever the session dictionary is updated (e.g., session["user_id"] = 1). Flask only writes the session cookie back to the client if modified is true.

Manual Modification Tracking

Because SecureCookieSession only tracks top-level changes to the dictionary, it cannot detect when mutable objects stored inside the session are changed. If you modify a nested list or dictionary, you must manually set the modified flag:

@app.route("/update_cart")
def update_cart():
# Modifying a nested list
session["cart"].append("item_id")
# Manual flag required for the change to be saved
session.modified = True
return "Cart updated"

The Session Interface

The SecureCookieSessionInterface manages the lifecycle of the session cookie. It is responsible for opening the session at the start of a request and saving it at the end.

Opening and Saving Sessions

  • open_session: Reads the session cookie from the request. It uses a signer to verify the signature and deserialize the data. If the signature is invalid or the cookie is missing, it returns a new, empty SecureCookieSession.
  • save_session: Determines if the session needs to be saved based on the modified flag and configuration. It serializes the session data, signs it, and sets the Set-Cookie header on the response.

Cryptographic Signing

Flask uses the itsdangerous module for signing. The get_signing_serializer method in SecureCookieSessionInterface creates a URLSafeTimedSerializer using the application's SECRET_KEY.

# Internal implementation detail from src/flask/sessions.py
def get_signing_serializer(self, app: Flask) -> URLSafeTimedSerializer | None:
if not app.secret_key:
return None
# ... setup keys and signer_kwargs ...
return URLSafeTimedSerializer(
keys,
salt=self.salt,
serializer=self.serializer,
signer_kwargs={
"key_derivation": self.key_derivation,
"digest_method": self.digest_method,
},
)

Security and Key Management

A SECRET_KEY is mandatory for signed sessions. If no secret key is set, Flask uses a NullSession, which raises a RuntimeError if you attempt to modify it.

Key Rotation

You can rotate secret keys without invalidating existing sessions by using SECRET_KEY_FALLBACKS. When verifying a session cookie, Flask will try the current SECRET_KEY first, then iterate through the fallback keys.

app.secret_key = "current-strong-key"
app.config["SECRET_KEY_FALLBACKS"] = ["old-key-1", "old-key-2"]

Serialization with Tagged JSON

Flask uses a specialized TaggedJSONSerializer (defined in src/flask/json/tag.py) for session data. This allows the session to store more Python types than standard JSON, including:

  • tuple
  • bytes
  • datetime objects
  • uuid.UUID
  • markupsafe.Markup

These types are "tagged" during serialization so they can be reconstructed accurately when the session is loaded. For example, a tuple is stored with a t tag to distinguish it from a list.

Configuration

The behavior of the session cookie is controlled by several configuration variables:

Config KeyDescription
SESSION_COOKIE_NAMEThe name of the cookie (default: 'session').
SESSION_COOKIE_DOMAINThe domain for the cookie.
SESSION_COOKIE_PATHThe path for the cookie.
SESSION_COOKIE_HTTPONLYPrevents client-side scripts from accessing the cookie (default: True).
SESSION_COOKIE_SECUREOnly sends the cookie over HTTPS (default: False).
SESSION_COOKIE_SAMESITERestricts cookie sending on cross-site requests (default: 'Lax').
PERMANENT_SESSION_LIFETIMEHow long a permanent session lasts (default: 31 days).
SESSION_REFRESH_EACH_REQUESTWhether to send the cookie on every request to refresh the expiration.

You can customize these in your application config:

app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)