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.
Try It Yourself
13 minVisit your blog, log in, look at the session cookie in DevTools. Base64-decode the first part and confirm you see your user_id.
If 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)
Switch to Flask-Session with the filesystem backend — sessions stored on disk, only an id cookie. (For very privacy-sensitive apps.)
Mini-Challenge · 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.
Homework
4 minHarden your blog: secret key from env var, cookie config tightened, logout via POST, "remember me" checkbox working, next redirect after login. Commit.