Using Flask Sessions
Flask exposes a session dict-like object:
from flask import sessionRequirements
Section titled “Requirements”Sessions require:
app.config["SECRET_KEY"]
Setting and reading values
Section titled “Setting and reading values”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")}Removing values
Section titled “Removing values”session.pop("favorite_color", None)Important constraints
Section titled “Important constraints”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
How Flask-Login uses session
Section titled “How Flask-Login uses session”Flask-Login stores:
- the logged-in user id
in the session so it persists across requests.
The session is a signed cookie, not server storage
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:
flowchart LR
V["session['user_id'] = 42"] --> S["serialise to JSON"]
S --> B["base64 encode"]
B --> G["sign with SECRET_KEY"]
G --> C["Set-Cookie: session=payload.timestamp.signature"]
C --> U["stored in the browser"]
U --> R["sent back on every request"]
R --> Vf{"signature valid?"}
Vf -->|"yes"| L["load the values"]
Vf -->|"no"| E["ignored - an empty session"]
Measured on a live response:
session=eyJyb2xlIjoiYWRtaW4iLCJ1c2VyX2lkIjo0Mn0.anhgvw.p1ahivGni_gpokhyDbSbrS2-QhY; HttpOnly; Path=/Three dot-separated segments: payload, timestamp, signature.
Anyone can read it
Section titled “Anyone can read it”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.
They cannot change it
Section titled “They cannot change it”The signature covers the payload, so a modified payload no longer verifies:
# 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:
app.config["SECRET_KEY"] = "a-different-key"
client.get("/whoami") # -> 'None None' every existing session is now invalidMeasured. 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.
Working with it
Section titled “Working with it”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.
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.
See it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
Where does Flask store the contents of the session by default?
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.
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.
-
The first segment of a session cookie base64-decodes to the JSON with role admin and user_id 42. What follows?
base64 is an encoding, not encryption. Store identity such as user_id, never anything the user should not see.
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.
-
An attacker replaces the payload with their own JSON and keeps the original signature. What does the app see?
Measured: the forged cookie produced None for every key. Tampering reduces an attacker to an anonymous visitor rather than raising an error.
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.
-
What happens to existing sessions when SECRET_KEY changes?
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.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.coffeeCtapch.feedbackHeading
pch.feedbackSubheading