Sessions & Cookies

HTTP is stateless: every request Bookshelf receives arrives with no memory of any request before it. The server that handles a visitor loading the book list has no idea, on its own, whether that same visitor just added a book thirty seconds ago, or is logged in at all — each request is a clean slate unless something is done to bridge that gap. Flask's answer is the session object, backed underneath by browser cookies.

The session Object

Flask's session behaves like a Python dictionary — you read and write keys on it exactly as you would a dict — but its contents persist across separate requests from the same visitor. Under the hood, Flask serializes whatever you put into session, cryptographically signs it, and stores the result in a cookie sent to the visitor's browser. On the next request, Flask reads that cookie back, verifies the signature, and reconstructs the session dictionary — all automatically, before your view function runs.

Signed, Not Encrypted — a Distinction Worth Sitting With

This is the single most important thing to understand about Flask's session: signed does not mean hidden. The cookie's contents are plainly readable by anyone who opens their browser's dev tools — session data is stored client-side, in the visitor's own browser. What the cryptographic signature guarantees is that if the cookie's contents are altered by the visitor (or anyone else) after Flask signed them, Flask will detect the mismatch and reject the tampered cookie outright.

Signed cookie (Flask's session)
Can the visitor read it?Yes — it's stored in their own browser, in plain view
Can the visitor alter it undetected?No — any change breaks the signature, and Flask discards the cookie
Never store in the session: passwords, unhashed tokens, credit card numbers, or anything else that would be a problem if the visitor themselves could read it — because they can. Store an identifier (like a user's id) and look the sensitive data up server-side instead.

Worked Example: Remembering a Recently Viewed Book

A small, genuinely useful bit of state for Bookshelf: remembering the last book a visitor looked at, so a "Jump back in" link can appear on their next visit.

# app.py
from flask import Flask, session, render_template

app = Flask(__name__)
app.config['SECRET_KEY'] = 'a-long-random-value-kept-out-of-source-control'

@app.route('/books/<int:book_id>')
def book_detail(book_id):
    book = Book.query.get_or_404(book_id)
    session['recently_viewed_book_id'] = book.id  # written on every visit
    return render_template('book_detail.html', book=book)

@app.route('/')
def home():
    recent_id = session.get('recently_viewed_book_id')  # read back on a later request
    recent_book = Book.query.get(recent_id) if recent_id else None
    return render_template('home.html', recent_book=recent_book)

Notice session.get('recently_viewed_book_id') rather than session['recently_viewed_book_id'] — exactly like a plain dict, .get() returns None instead of raising a KeyError when a visitor arrives at home without ever having viewed a book.

Why session Requires a SECRET_KEY

Without app.config['SECRET_KEY'] set, any attempt to write to session raises a RuntimeError — Flask refuses to sign a cookie with no key to sign it with. This is the same SECRET_KEY covered in Configuration & Environments and used by Flask-WTF's CSRF protection in Form Validation & CSRF — one configuration value doing cryptographic duty for both the session cookie and the CSRF token. It should be a long, random, unguessable string, generated once and kept out of source control, exactly as that lesson describes.

Note: This session-handling code is correct, real Flask usage — but this website's own project doesn't run a Flask server, so no session cookie is actually set or read on this page.

Plain Cookies, Set Directly

Not every small piece of per-visitor state belongs in the signed session — a display preference like a light/dark theme toggle, for instance, doesn't need cryptographic signing at all; there's nothing sensitive about it, and it doesn't matter if a visitor edits it in dev tools. For cases like this, Flask lets you set a cookie directly on a response object, built with make_response() (the same response-building tool covered in Request & Response Objects):

# app.py
from flask import make_response, request

@app.route('/set-theme/<theme>')
def set_theme(theme):
    resp = make_response('Theme updated')
    resp.set_cookie('theme', theme)  # plain cookie, not signed via session
    return resp

@app.route('/')
def home():
    theme = request.cookies.get('theme', 'light')  # default if the cookie was never set
    return render_template('home.html', theme=theme)

Reading it back is request.cookies.get('theme') — a plain dictionary-style lookup on the incoming request, with an optional default for visitors who've never set one.

Cookie Security Options: SESSION_COOKIE_SECURE and SESSION_COOKIE_HTTPONLY

Two config values control how Flask's session cookie itself behaves in the browser:

Config keyWhat it does
SESSION_COOKIE_HTTPONLYWhen True (the default), blocks JavaScript from reading the session cookie at all — a defense against certain cross-site scripting attacks stealing it
SESSION_COOKIE_SECUREWhen True, tells the browser to only ever send the cookie over HTTPS, never plain HTTP

Both matter far more in production than in local development, and the full picture of tuning these safely — alongside the rest of Bookshelf's security posture — is covered later in Security Best Practices.

Clearing Session Data on Logout

Ending a session — most commonly, when a user logs out — means removing what's stored in it. Two options exist depending on how much you want to clear:

@app.route('/logout')
def logout():
    session.pop('user_id', None)  # remove just this one key
    # or, to remove everything the session was holding:
    # session.clear()
    return redirect(url_for('home'))

session.pop('user_id', None) mirrors ordinary dict behavior — removing the key if present, doing nothing if it's already absent, with no KeyError either way. session.clear() is the blunter tool, wiping the whole session at once. In a real Bookshelf login system, Flask-Login (covered in Authentication with Flask-Login) manages most of this session bookkeeping for you — but knowing what it's doing underneath makes that extension far less of a black box.

Coming up next: Flash Messages builds directly on the session mechanism from this page to show a one-time confirmation message right after a redirect.