Logins, carts and flash messages all depend on remembering the user between requests.
HTTP is stateless, so Flask remembers users with the session object: a dict-like store that Flask serializes into a cookie. It is signed with your SECRET_KEY (using itsdangerous), so users can't tamper with it, but it is not encrypted: anyone can base64-decode the cookie and read its contents. Never store passwords or secrets in it; store an identifier like user_id.
Set a long random key from the environment, for example generated with python -c "import secrets; print(secrets.token_hex())". If the key leaks, attackers can forge sessions and log in as anyone. Flask 3.1 adds SECRET_KEY_FALLBACKS, a list of old keys still accepted for reading, so you can rotate keys without logging everybody out.
Behavior to know:
- Sessions last until the browser closes, unless you set
session.permanent = True; thenPERMANENT_SESSION_LIFETIME(31 days by default) applies. - Flask detects assignment (
session["cart"] = ...) but not in-place mutation of nested values. Aftersession["cart"].append(x), setsession.modified = True. - Cookies are limited to about 4 KB, so keep sessions small.
session.clear()on logout.
Cookie flags matter: SESSION_COOKIE_HTTPONLY is on by default; set SESSION_COOKIE_SECURE=True (HTTPS only) and SESSION_COOKIE_SAMESITE="Lax" in production.
For large or server-revocable sessions, extensions such as Flask-Session store the data in Redis or a database and put only an ID in the cookie.
Example
from flask import Flask, session
app = Flask(__name__)
app.config.update(SECRET_KEY="change-me", SESSION_COOKIE_SAMESITE="Lax")
@app.post("/cart/<item>")
def add(item):
cart = session.setdefault("cart", [])
cart.append(item)
session.modified = True # in-place change: tell Flask to save it
return {"cart": cart}
@app.post("/logout")
def logout():
session.clear()
return {"ok": True}
c = app.test_client()
c.post("/cart/apple")
print(c.post("/cart/pear").json) # {'cart': ['apple', 'pear']}
with c.session_transaction() as s: # peek at the stored session
print(s["cart"]) # ['apple', 'pear']
c.post("/logout")
print(c.post("/cart/fig").json) # {'cart': ['fig']}Remember
- session is a signed (not encrypted) cookie: readable, not forgeable
- Use a long random SECRET_KEY from the environment
- Set session.modified = True after mutating nested values
Watch out for
- Storing sensitive data in the session, assuming it is encrypted.