Learning Goals
3 min- Understand the signed cookie behind
flask.session. - Set
SESSION_COOKIE_SECURE,HTTPONLY,SAMESITE. - Distinguish session vs permanent_session; control lifetime.
- Implement a clean logout that invalidates the session and shows feedback.
Warm-Up · Look at the Cookie
5 minOpen DevTools → Application → Cookies. You'll see a single session cookie holding a base64 blob like:
session eyJ1c2VyX2lkIjo3fQ.YXqEvw.s3-D7sJpYbq...
That is your session dict, base64-encoded, with a signature appended. Anyone can decode it (it's not encrypted), but only your server can change it (the signature requires the secret_key).
Sessions are signed, not encrypted. Treat the contents as readable. Never put secrets in there — only references (user_id, role) and small UI bits.
New Concept · secret_key, cookies, lifetime
14 minProduction-grade config
import os, datetime from flask import Flask app = Flask(__name__) app.config.update( SECRET_KEY = os.environ["FLASK_SECRET"], # NOT hard-coded SESSION_COOKIE_HTTPONLY = True, # JS can't read it SESSION_COOKIE_SECURE = True, # only over HTTPS SESSION_COOKIE_SAMESITE = "Lax", # CSRF mitigation PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=14), )
Permanent vs not
# Default: session dies when browser closes session["user_id"] = uid # Or, persist across browser restarts using PERMANENT_SESSION_LIFETIME session.permanent = True session["user_id"] = uid
Generating a secret key
$ python -c "import secrets; print(secrets.token_hex(32))" $ export FLASK_SECRET=<paste>
Logging out
@app.route("/logout", methods=["POST"]) def logout(): session.clear() flash("Logged out.") return redirect(url_for("index"))
session.clear() wipes everything. Use POST for logout so a stray <img src="/logout"> on another site can't log you out (CSRF).
Inspecting from a route
@app.route("/whoami") def whoami(): return dict(session)
The "remember me" checkbox
@app.route("/login", methods=["POST"]) def login(): ... remember = request.form.get("remember") == "on" session.permanent = remember session["user_id"] = user["id"] return redirect(url_for("index"))
Worked Example · Polished Logout
12 min# routes.py — final auth bits from flask import render_template, request, session, redirect, url_for, flash @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": email = request.form.get("email", "").strip().lower() pw = request.form.get("password", "") remember = request.form.get("remember") == "on" user = auth.authenticate(email, pw) if not user: flash("Wrong email or password.") return render_template("login.html", email=email) session.permanent = remember session["user_id"] = user["id"] flash("Logged in.") next_url = request.args.get("next") or url_for("index") return redirect(next_url) return render_template("login.html") @app.route("/logout", methods=["POST"]) def logout(): session.clear() flash("Logged out.") return redirect(url_for("index"))
<!-- templates/base.html nav (excerpt) -->
{% if current_user %}
<form method="post" action="{{ url_for('logout') }}" style="display:inline">
<button type="submit" class="link-button">Logout ({{ current_user.email }})</button>
</form>
{% else %}
<a href="{{ url_for('login') }}">Login</a>
·
<a href="{{ url_for('signup') }}">Sign up</a>
{% endif %}
<!-- templates/login.html (excerpt) -->
<input name="remember" id="remember" type="checkbox">
<label for="remember">Remember me</label>Read the diff
Three production touches: logout is POST-only (CSRF-safer), the next query parameter sends users back to the page they tried to visit, and "remember me" toggles permanent sessions.
Basic
5 minVisit your blog, log in, look at the session cookie in DevTools. Base64-decode the first part and confirm you see your user_id.
Challenge 1
4 minIf a guest tries to hit a @login_required route, redirect to /login?next=/that/path. After login, send them back.
Hint
# in login_required return redirect(url_for("login", next=request.path)) # in login() after success next_url = request.args.get("next") or url_for("index") return redirect(next_url)
Challenge 2
4 minSwitch to Flask-Session with the filesystem backend — sessions stored on disk, only an id cookie. (For very privacy-sensitive apps.)
Challenge 3 · CSRF Token
8 minAdd a CSRF token to every form. Generate with secrets.token_urlsafe, store in session["csrf"], render as a hidden input, validate on POST. (Or use Flask-WTF for the real-world version.)
Recap
3 minSessions are signed cookies — readable, not editable. Keep SECRET_KEY out of source. Set HTTPONLY + SECURE + SAMESITE in prod. session.clear() for logout. POST your logout to dodge CSRF. Tomorrow: deployment.
Extra Mission
4 minHarden your blog: secret key from env var, cookie config tightened, logout via POST, "remember me" checkbox working, next redirect after login. Commit.