Sessions and cookies

Flask · Practical · 10 min

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; then PERMANENT_SESSION_LIFETIME (31 days by default) applies.
  • Flask detects assignment (session["cart"] = ...) but not in-place mutation of nested values. After session["cart"].append(x), set session.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.

Check yourself

  1. A user decodes their Flask session cookie. What can they do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: flask.palletsprojects.com/en/stable/quickstart/#sessions