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.
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.
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.
<!-- 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.
# 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().
Flask-Login, LoginManager, UserMixin, login_required, current_user