Skip to content

Using Flask Sessions

Flask exposes a session dict-like object:

python
from flask import session

Sessions require:

  • app.config["SECRET_KEY"]
python
from flask import Flask, session
 
app = Flask(__name__)
app.config["SECRET_KEY"] = "dev-key"
 
 
@app.route("/set")
def set_value():
    session["favorite_color"] = "blue"
    return "ok"
 
 
@app.route("/get")
def get_value():
    return {"favorite_color": session.get("favorite_color")}
python
session.pop("favorite_color", None)

Because Flask’s default session is stored in a cookie:

  • keep session data small
  • don’t store secrets in sessions (client can read)
  • sign integrity is provided, confidentiality is not

Flask-Login stores:

  • the logged-in user id

in the session so it persists across requests.

Section titled “The session is a signed cookie, not server storage”

Flask keeps the whole session in the cookie, on the user’s machine. Nothing is stored on the server:

diagram Diagram mermaid

Measured on a live response:

Set-Cookie
session=eyJyb2xlIjoiYWRtaW4iLCJ1c2VyX2lkIjo0Mn0.anhgvw.p1ahivGni_gpokhyDbSbrS2-QhY; HttpOnly; Path=/

Three dot-separated segments: payload, timestamp, signature.

decode.py
import base64
payload = cookie_value.split(".")[0]
base64.urlsafe_b64decode(payload + "==")
# b'{"role":"admin","user_id":42}'

Measured — that is the actual decoded content, with no key and no effort. The payload is base64, which is an encoding, not encryption.

The signature covers the payload, so a modified payload no longer verifies:

forge.py
# swap the payload for {"user_id": 1, "role": "admin"}, keep the original signature
client.set_cookie("session", forged)
client.get("/whoami")      # -> 'None None'

Measured: the forged cookie was rejected entirely and the session came back empty — not an error, just no values. Tampering downgrades an attacker to an anonymous visitor.

That guarantee rests entirely on SECRET_KEY:

rotate.py
app.config["SECRET_KEY"] = "a-different-key"
client.get("/whoami")      # -> 'None None'   every existing session is now invalid

Measured. Rotating the key logs everybody out — which is the emergency response if it ever leaks, and the reason it must be stable across restarts and identical across every worker process. A key generated at startup means users are logged out whenever you deploy.

views.py
from flask import session
 
session["user_id"] = user.id      # set
session.get("user_id")            # read, None if absent
session.pop("user_id", None)      # remove one key
session.clear()                   # log out completely
 
session.permanent = True          # honour PERMANENT_SESSION_LIFETIME
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=7)

Without session.permanent, the cookie is a session cookie: it disappears when the browser closes.

cookie_flags.py
app.config.update(
    SESSION_COOKIE_HTTPONLY=True,   # default: JavaScript cannot read it
    SESSION_COOKIE_SECURE=True,     # HTTPS only — set this in production
    SESSION_COOKIE_SAMESITE="Lax",  # limits cross-site sending
)

HttpOnly is on by default, and appeared in the measured header above. Secure is not — without it the cookie is sent over plain HTTP, where anyone on the network can copy it and use it, since a stolen cookie is as good as the password.

sketch What is actually in your session cookie p5.js
The payload is base64, readable by anyone holding the cookie. The signature stops it being changed, but does not hide it.
pch.quizTag pch.quizDefaultTitle
  1. Where does Flask store the contents of the session by default?

    pch.quizShowAnswer

    B — in the cookie itself, serialised and signed with SECRET_KEY — The cookie carries payload, timestamp and signature. Nothing is kept server-side, which is why the session survives a restart and why its size is limited by the 4 KB cookie limit.

  2. The first segment of a session cookie base64-decodes to the JSON with role admin and user_id 42. What follows?

    pch.quizShowAnswer

    B — the session is signed but not encrypted, so anyone holding the cookie can read every value in it — base64 is an encoding, not encryption. Store identity such as user_id, never anything the user should not see.

  3. An attacker replaces the payload with their own JSON and keeps the original signature. What does the app see?

    pch.quizShowAnswer

    B — an empty session; the signature no longer matches the payload, so the cookie is ignored — Measured: the forged cookie produced None for every key. Tampering reduces an attacker to an anonymous visitor rather than raising an error.

  4. What happens to existing sessions when SECRET_KEY changes?

    pch.quizShowAnswer

    B — every existing session becomes invalid, so all users are logged out — Measured: the same client read None after the key changed. That is the emergency response if the key leaks, and the reason a key generated at startup logs everyone out on each deploy.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading