Skip to main content

Overview

Flask provides session management through signed cookies, allowing you to store user-specific data between requests securely.

Session Basics

Sessions work like a dictionary and are available globally:
from flask import Flask, session, redirect, url_for

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # Required!

@app.route('/login', methods=['POST'])
def login():
    # Set session data
    session['user_id'] = user.id
    session['username'] = user.username
    session.permanent = True  # Use permanent session lifetime
    return redirect('/dashboard')

@app.route('/dashboard')
def dashboard():
    # Read session data
    if 'user_id' not in session:
        return redirect('/login')
    
    username = session.get('username')
    return f'Welcome, {username}!'

@app.route('/logout')
def logout():
    # Clear session
    session.pop('user_id', None)
    session.pop('username', None)
    # Or clear all: session.clear()
    return redirect('/')

Secret Key

The secret key is required for sessions to work:
# Development - hardcoded (NOT for production!)
app.secret_key = 'dev-secret-key-change-in-production'

# Production - from environment
import os
app.secret_key = os.environ.get('SECRET_KEY')

# Production - from config file
app.config.from_object('config.ProductionConfig')

# Generate a good secret key
import secrets
print(secrets.token_hex(32))  # Use this in production
Never commit secret keys to version control! Use environment variables or config files excluded from git.

Session Interface

From sessions.py:100-271, Flask uses SecureCookieSessionInterface by default:
class SecureCookieSessionInterface(SessionInterface):
    """The default session interface that stores sessions in signed cookies
    through the itsdangerous module."""
    
    salt = "cookie-session"
    digest_method = staticmethod(hashlib.sha1)
    key_derivation = "hmac"
    serializer = session_json_serializer
    session_class = SecureCookieSession

How It Works

  1. Session data is serialized to JSON
  2. JSON is signed with itsdangerous using your secret key
  3. Signed data is stored in a cookie
  4. On subsequent requests, cookie is validated and deserialized

Session Configuration

Configure session behavior:
from datetime import timedelta

# Session lifetime (default: 31 days)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)

# Cookie configuration
app.config['SESSION_COOKIE_NAME'] = 'session'
app.config['SESSION_COOKIE_DOMAIN'] = None
app.config['SESSION_COOKIE_PATH'] = '/'
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = False  # True in production with HTTPS
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'  # 'Strict', 'Lax', or None
app.config['SESSION_COOKIE_PARTITIONED'] = False

# Refresh session on every request
app.config['SESSION_REFRESH_EACH_REQUEST'] = True

# Cookie size limit (default: 4093 bytes)
app.config['MAX_COOKIE_SIZE'] = 4093

Permanent Sessions

From sessions.py:24-54, control session lifetime:
@app.route('/login', methods=['POST'])
def login():
    session['user_id'] = user.id
    
    if request.form.get('remember_me'):
        session.permanent = True  # Use PERMANENT_SESSION_LIFETIME
    else:
        session.permanent = False  # Expires when browser closes
    
    return redirect('/dashboard')

Session Mixin

From sessions.py:24-55, sessions implement SessionMixin:
class SessionMixin:
    permanent = False  # Is this a permanent session?
    modified = True    # Has the session been modified?
    accessed = False   # Has the session been accessed?
    new = False        # Is this a new session?

Session Properties

@app.route('/check-session')
def check_session():
    # Check if session is permanent
    if session.permanent:
        print("Permanent session")
    
    # Session is automatically marked as modified when changed
    session['key'] = 'value'  # modified = True
    
    # Manually mark as modified (for mutable values)
    session['list'] = []
    session['list'].append('item')  # This won't trigger modified!
    session.modified = True  # Mark it manually

Session Security

Secure Cookies

From sessions.py:303-335, sessions are cryptographically signed:
def get_signing_serializer(self, app):
    if not app.secret_key:
        return None
    
    keys = []
    if fallbacks := app.config["SECRET_KEY_FALLBACKS"]:
        keys.extend(fallbacks)
    keys.append(app.secret_key)
    
    return URLSafeTimedSerializer(
        keys,
        salt=self.salt,
        serializer=self.serializer,
        signer_kwargs={
            "key_derivation": self.key_derivation,
            "digest_method": self.digest_method,
        },
    )

Key Rotation

Rotate secret keys without breaking existing sessions:
app.config['SECRET_KEY'] = 'new-secret-key'
app.config['SECRET_KEY_FALLBACKS'] = ['old-secret-key-1', 'old-secret-key-2']

HTTPS-Only Cookies

For production, always use secure cookies:
# In production config
app.config['SESSION_COOKIE_SECURE'] = True  # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True  # No JavaScript access
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'  # CSRF protection

Session Operations

Setting Values

@app.route('/set')
def set_session():
    session['username'] = 'john'
    session['preferences'] = {'theme': 'dark', 'lang': 'en'}
    session['cart_items'] = [1, 2, 3]
    return 'Session set'

Getting Values

@app.route('/get')
def get_session():
    # With default value
    username = session.get('username', 'guest')
    
    # Check if key exists
    if 'user_id' in session:
        user_id = session['user_id']
    
    # Iterate keys
    for key in session:
        print(f'{key}: {session[key]}')
    
    return f'Hello, {username}'

Modifying Values

@app.route('/modify')
def modify_session():
    # Simple values auto-mark as modified
    session['counter'] = session.get('counter', 0) + 1
    
    # Mutable values need manual marking
    if 'items' not in session:
        session['items'] = []
    
    session['items'].append('new_item')
    session.modified = True  # Important!
    
    return 'Modified'

Removing Values

@app.route('/remove')
def remove_from_session():
    # Remove specific key
    session.pop('username', None)
    
    # Clear all session data
    session.clear()
    
    return 'Removed'

Flash Messages

Flask provides flash messages stored in the session:
from flask import flash, get_flashed_messages

@app.route('/process', methods=['POST'])
def process():
    if success:
        flash('Operation successful!', 'success')
    else:
        flash('Operation failed!', 'error')
    
    return redirect('/dashboard')

@app.route('/dashboard')
def dashboard():
    # Messages are automatically retrieved and removed from session
    return render_template('dashboard.html')
In templates:
{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    {% for category, message in messages %}
      <div class="alert alert-{{ category }}">
        {{ message }}
      </div>
    {% endfor %}
  {% endif %}
{% endwith %}

Custom Session Interface

Implement custom session storage (database, Redis, etc.):
from flask.sessions import SessionInterface, SessionMixin
from uuid import uuid4

class RedisSession(dict, SessionMixin):
    pass

class RedisSessionInterface(SessionInterface):
    def __init__(self, redis_client, prefix='session:'):
        self.redis = redis_client
        self.prefix = prefix
    
    def open_session(self, app, request):
        sid = request.cookies.get(app.config['SESSION_COOKIE_NAME'])
        if sid:
            data = self.redis.get(f'{self.prefix}{sid}')
            if data:
                return RedisSession(json.loads(data))
        
        return RedisSession()
    
    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        
        if not session:
            # Delete cookie if session is empty
            response.delete_cookie(
                app.config['SESSION_COOKIE_NAME'],
                domain=domain,
                path=path
            )
            return
        
        sid = str(uuid4())
        self.redis.setex(
            f'{self.prefix}{sid}',
            app.permanent_session_lifetime.total_seconds(),
            json.dumps(dict(session))
        )
        
        response.set_cookie(
            app.config['SESSION_COOKIE_NAME'],
            sid,
            httponly=True,
            secure=app.config['SESSION_COOKIE_SECURE']
        )

# Use custom interface
import redis
redis_client = redis.Redis()
app.session_interface = RedisSessionInterface(redis_client)

Null Sessions

From sessions.py:83-97, when no secret key is set:
class NullSession(SecureCookieSession):
    """Session used when no secret key is set."""
    
    def _fail(self, *args, **kwargs):
        raise RuntimeError(
            "The session is unavailable because no secret "
            "key was set. Set the secret_key on the "
            "application to something unique and secret."
        )
    
    __setitem__ = __delitem__ = clear = pop = popitem = update = setdefault = _fail

Session Access Tracking

From sessions.py:46-54 and ctx.py:396-403, sessions track access:
@app.route('/check')
def check():
    # Simply accessing session marks it as accessed
    if session:
        pass  # session.accessed = True
    
    # This adds Vary: Cookie header to response
    return 'OK'

Best Practices

Strong Secret Keys

Use cryptographically strong random keys (32+ bytes)

HTTPS in Production

Always use SESSION_COOKIE_SECURE=True with HTTPS

Minimal Data

Store only essential data; sessions are sent with every request

Mark Modified

Manually set session.modified when changing mutable values

Common Patterns

User Authentication

@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form['username'], request.form['password'])
    if user:
        session['user_id'] = user.id
        session['logged_in'] = True
        flash('Login successful!', 'success')
        return redirect('/dashboard')
    flash('Invalid credentials', 'error')
    return redirect('/login')

@app.before_request
def load_user():
    if 'user_id' in session:
        g.user = get_user(session['user_id'])
    else:
        g.user = None

Shopping Cart

@app.route('/cart/add/<int:product_id>')
def add_to_cart(product_id):
    if 'cart' not in session:
        session['cart'] = []
    
    session['cart'].append(product_id)
    session.modified = True  # Important for lists!
    
    flash(f'Added product {product_id} to cart', 'success')
    return redirect('/products')

Build docs developers (and LLMs) love