Flask-Login Setup

Difficulty: Advanced

Flask-Login is the standard extension for handling user authentication in Flask. It manages user sessions, provides the login_required decorator, and makes the current user accessible throughout your application via the current_user proxy. It handles the session management layer while leaving the actual authentication logic (verifying credentials) to you.

Flask-Login needs four things to work: a LoginManager instance configured on your app, a User model that implements certain properties, a user_loader callback that loads a user from the session, and view functions for login and logout.

The LoginManager is initialized with your app (or via init_app for the factory pattern). You configure it with login_view (the endpoint for the login page), login_message (flash message when redirected), and login_message_category (flash message category).

Your User model must implement four properties/methods: is_authenticated (returns True if the user is logged in), is_active (returns True if the account is active), is_anonymous (returns True for anonymous users), and get_id() (returns a unique string identifier). The UserMixin class provides default implementations of all four, so you just need to inherit from it.

The user_loader callback tells Flask-Login how to load a user from the session. Flask-Login stores only the user ID in the session. On each request, it calls user_loader with that ID to get the full user object. This function should query your database and return the user or None.

The login_user() function logs a user in by storing their ID in the session. The logout_user() function logs them out by clearing the session. The login_required decorator protects views that require authentication, redirecting anonymous users to the login page.

current_user is a proxy that returns the logged-in user object (loaded by user_loader) or an AnonymousUserMixin for unauthenticated users. It is available in view functions and Jinja2 templates. You can check current_user.is_authenticated to determine if the user is logged in.

Code examples

Basic Flask-Login Setup

from flask import Flask, redirect, url_for, request, flash, render_template
from flask_login import LoginManager, UserMixin, login_user, logout_user, \
    login_required, current_user
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message = 'Please log in to access this page.'

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

LoginManager connects to the app and is configured with the login endpoint. User inherits UserMixin for required properties. The user_loader callback loads users by ID from the database for each request.

Login and Logout Views

from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.security import check_password_hash

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))

    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        remember = 'remember' in request.form

        user = User.query.filter_by(username=username).first()
        if user and check_password_hash(user.password_hash, password):
            login_user(user, remember=remember)
            # Redirect to the page they were trying to access
            next_page = request.args.get('next')
            return redirect(next_page or url_for('dashboard'))

        flash('Invalid username or password', 'error')
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('index'))

@app.route('/dashboard')
@login_required
def dashboard():
    return f'Welcome, {current_user.username}!'

login_user() stores the user ID in the session. The remember parameter keeps the session active after the browser closes. login_required redirects to login_view if the user is not authenticated. The 'next' parameter preserves the original destination.

current_user in Templates

<!-- templates/base.html -->
<nav>
    <a href="{{ url_for('index') }}">Home</a>

    {% if current_user.is_authenticated %}
        <a href="{{ url_for('dashboard') }}">Dashboard</a>
        <span>{{ current_user.username }}</span>
        <a href="{{ url_for('logout') }}">Logout</a>
    {% else %}
        <a href="{{ url_for('login') }}">Login</a>
        <a href="{{ url_for('register') }}">Register</a>
    {% endif %}
</nav>

{% with messages = get_flashed_messages(with_categories=true) %}
    {% for category, message in messages %}
        <div class="alert alert-{{ category }}">{{ message }}</div>
    {% endfor %}
{% endwith %}

current_user is automatically available in all Jinja2 templates. Check is_authenticated to show different navigation for logged-in vs anonymous users. Flash messages display login feedback.

Factory Pattern Setup

# app/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'

# app/models/user.py
from flask_login import UserMixin
from app.extensions import db, login_manager

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True)
    password_hash = db.Column(db.String(256))

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# app/__init__.py
from flask import Flask
from app.extensions import db, login_manager

def create_app():
    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

    db.init_app(app)
    login_manager.init_app(app)

    from app.views.auth import auth_bp
    app.register_blueprint(auth_bp)

    return app

With the factory pattern, create LoginManager in extensions.py without an app. The user_loader goes in the models file. init_app() binds the login manager to the app in create_app().

Key points

Concepts covered

Flask-Login, LoginManager, UserMixin, login_required, current_user