Session Management

Difficulty: Advanced

Session management controls how long users stay logged in, how sessions are protected, and how sensitive actions require re-authentication. Flask-Login provides several features for managing authentication sessions securely.

The remember me feature lets users stay logged in after closing their browser. By default, Flask sessions use session cookies that expire when the browser closes. When you call login_user(user, remember=True), Flask-Login sets a persistent remember cookie alongside the session cookie. This cookie contains a secure token that Flask-Login uses to restore the session.

The REMEMBER_COOKIE_DURATION config (default 365 days) controls how long the remember cookie lasts. REMEMBER_COOKIE_SECURE ensures it is only sent over HTTPS. REMEMBER_COOKIE_HTTPONLY prevents JavaScript access. Always set these in production.

Flask-Login's session protection feature detects session hijacking attempts. When set to 'strong', Flask-Login stores a hash of the user's IP address and user agent in the session. If these change, the session is invalidated. 'basic' mode stores only a hash of the user agent. 'None' disables session protection entirely.

Fresh logins are an important security concept. Some sensitive actions (changing password, adding payment method) should require a recent login, not just any valid session. Flask-Login tracks whether the current login is 'fresh' (the user just entered credentials) or 'non-fresh' (restored from a remember cookie). The fresh_login_required decorator enforces this.

You can customize the anonymous user by setting login_manager.anonymous_user to a custom class. This class should implement is_authenticated (False), is_active (False), is_anonymous (True), and get_id() (returns None). This is useful for giving anonymous users certain default permissions or attributes.

For server-side session storage (more secure than client-side cookies), use Flask-Session. It stores session data in Redis, Memcached, or a database. This removes the 4 KB cookie size limit and prevents clients from seeing session data.

Code examples

Remember Me Configuration

from flask import Flask
from flask_login import LoginManager, login_user
from datetime import timedelta

app = Flask(__name__)
app.config.update(
    SECRET_KEY='your-secret-key',
    REMEMBER_COOKIE_DURATION=timedelta(days=14),
    REMEMBER_COOKIE_SECURE=True,
    REMEMBER_COOKIE_HTTPONLY=True,
    REMEMBER_COOKIE_SAMESITE='Lax',
)

login_manager = LoginManager(app)

@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form)  # Your auth logic
    if user:
        remember = request.form.get('remember') == 'on'
        login_user(user, remember=remember)
        return redirect(url_for('dashboard'))
    return 'Login failed', 401

Remember me keeps the session alive for REMEMBER_COOKIE_DURATION (14 days here). Secure and httponly flags protect the cookie. The checkbox value is 'on' when checked.

Session Protection

from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
login_manager = LoginManager(app)

# Session protection modes:
# 'basic' - regenerate session if user agent changes
# 'strong' - regenerate session if user agent OR IP changes
# None - no session protection
login_manager.session_protection = 'strong'

# When session is invalidated, redirect here
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Your session has expired. Please log in again.'

Strong session protection detects IP or user agent changes and invalidates the session, protecting against session hijacking. This may cause issues for mobile users whose IP changes frequently.

Fresh Login Requirement

from flask import Flask, redirect, url_for, request
from flask_login import LoginManager, login_user, fresh_login_required, \
    login_required, confirm_login, current_user

app = Flask(__name__)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.needs_refresh_message = 'Please re-authenticate for this action.'
login_manager.refresh_view = 'reauthenticate'

@app.route('/settings')
@login_required
def settings():
    # Any logged-in user can access this
    return 'General Settings'

@app.route('/change-password', methods=['GET', 'POST'])
@fresh_login_required
def change_password():
    # Only recently authenticated users can access this
    return 'Change Password Form'

@app.route('/reauthenticate', methods=['GET', 'POST'])
@login_required
def reauthenticate():
    if request.method == 'POST':
        password = request.form['password']
        if current_user.check_password(password):
            confirm_login()  # Mark session as fresh
            return redirect(request.args.get('next') or url_for('settings'))
    return 'Enter your password to continue'

fresh_login_required protects sensitive actions. Users restored from a remember cookie have a non-fresh session and must re-enter their password. confirm_login() marks the session as fresh again.

Custom Anonymous User

from flask_login import LoginManager, AnonymousUserMixin

class CustomAnonymousUser(AnonymousUserMixin):
    """Custom anonymous user with default permissions."""

    @property
    def is_admin(self):
        return False

    @property
    def permissions(self):
        return ['read']  # Anonymous users can only read

    @property
    def theme(self):
        return 'light'  # Default theme

login_manager = LoginManager()
login_manager.anonymous_user = CustomAnonymousUser

# Now in templates and views:
# current_user.permissions works for both logged-in and anonymous users
# current_user.is_admin works for both

Custom anonymous users let you check permissions consistently without always checking is_authenticated first. current_user.is_admin works for both logged-in users (returns their actual value) and anonymous users (returns False).

Key points

Concepts covered

Remember Me, Session Lifetime, Fresh Login, Session Protection, Anonymous Users