PyFu

Flask Default Session for Authentication and Authorization

Python Web Development Frameworks

Introduction

Flask’s session-based authentication uses secure, signed cookies to store user session data on the client side. This system enables simple and effective user login functionality without requiring a database or external dependencies.

Like JSON Web Tokens (JWTs), Flask sessions are Base64-encoded and cryptographically signed using the app’s secret_key, making them tamper-proof but not encrypted, this makes their content is readable by the client.

Unlike JWTs, Flask session cookies are custom to Flask, simpler in structure, and tightly integrated into its request lifecycle, making them ideal for traditional web applications that require lightweight, stateful authentication.

Code

from flask import Flask, request, session, redirect, url_for, render_template_string

app = Flask(__name__)
app.secret_key = 'your_secret_key'  # Required for session management

# Dummy user credentials (in real apps, use a database and hashed passwords)
USERS = {
    "admin": "password123",
    "user": "pass"
}

# HTML template for login
login_form = """
<!doctype html>
<title>Login</title>
<h2>Login</h2>
<form method="post">
  <input name="username" placeholder="Username" required>
  <input name="password" type="password" placeholder="Password" required>
  <input type="submit" value="Login">
</form>
<p>{{ error }}</p>
"""

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = ''
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        if USERS.get(username) == password:
            session['user'] = username
            return redirect(url_for('dashboard'))
        error = 'Invalid credentials'
    return render_template_string(login_form, error=error)

@app.route('/dashboard')
def dashboard():
    if 'user' not in session:
        return redirect(url_for('login'))
    return f"Hello, {session['user']}! Welcome to your dashboard. <a href='/logout'>Logout</a>"

@app.route('/logout')
def logout():
    session.pop('user', None)
    return redirect(url_for('login'))

if __name__ == '__main__':
    app.run(debug=True)

Why Flask sessions matter from an offensive security perspective

The entire security of this scheme is one value: app.secret_key. The session cookie is signed, not encrypted, so its security model is “the client can read it but cannot forge it.” Both halves of that sentence are attacker-relevant.

It is readable. The cookie is just Base64-encoded data plus a signature. Anyone holding a session cookie can decode the payload and see exactly what the app trusts, often {"user": "admin"}, role flags, or user IDs. That tells you precisely which fields to target.

# A Flask session cookie decodes without any key. The signature only stops forgery, not reading.
from flask.sessions import SecureCookieSessionInterface
# In practice you would use a tool like `flask-unsign --decode --cookie '<cookie>'`

It is only as unforgeable as the key is unguessable. The example sets app.secret_key = 'your_secret_key'. A hardcoded, weak, or leaked key collapses the whole boundary: an attacker who recovers it can sign a cookie that says whatever they want and the server will trust it. The standard path is to grab any valid cookie, brute-force the key offline against the signature, then mint an admin session:

# Recover a weak/guessable secret from a captured cookie, then forge an admin session.
flask-unsign --unsign --cookie '<captured-cookie>' --wordlist /path/to/wordlist.txt
flask-unsign --sign --cookie "{'user': 'admin'}" --secret '<recovered-secret>'

This is the same offline-signing-key attack as Cracking Weak JWT Signing Keys, applied to Flask’s cookie format instead of JWT. Keys end up recoverable for boring reasons: committed to git, copied from a tutorial ('your_secret_key', 'change-me', 'dev'), derived from something predictable, or short enough to brute-force.

Two consequences follow directly:

  • Forged identity. A signed cookie is the authentication, so a forged cookie is full authentication bypass. If the app also stores authorization data in the session (is_admin, role), forging the cookie is privilege escalation in the same step. See Broken Access Control in Flask Applications.
  • Never put secrets in the session. Because the payload is readable, anything sensitive placed in the session (API keys, internal IDs, PII) leaks to every client holding the cookie.

When you assess a Flask app, the secret key is the first thing to hunt for and the first thing to test for weakness. Everything the session protects rests on it. The deserialization angle, when an app stores richer objects in the session or swaps the cookie store for a pickle-backed one, is in Authentication Bypass via Unsafe Python Deserialization.

Mitigation

Generate app.secret_key from a high-entropy random source, load it from the environment or a secrets manager rather than committing it, and rotate it if it is ever exposed, since the entire signing boundary collapses the moment the key is guessable or leaked. Store only an identifier in the session and look up authorization server-side on each request instead of trusting role flags in the cookie, and harden the cookie flags so it cannot ride over plaintext or be read by client script. For richer or higher-value state, move to a server-side session store so the client holds only an opaque key and never the data itself.

import os
from flask import Flask

app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET_KEY"]   # 32+ random bytes, never hardcoded

app.config.update(
    SESSION_COOKIE_HTTPONLY=True,    # not readable from JavaScript
    SESSION_COOKIE_SECURE=True,      # HTTPS only
    SESSION_COOKIE_SAMESITE="Lax",
)