User Authentication With Flask-Login
Introduction
Most apps need to know who is logged in. Flask-Login manages user sessions: login_user, logout_user, @login_required, and current_user in views and templates. This chapter stores hashed passwords with Werkzeug, implements registration and login, and adds simple role-based access control.
Prerequisites
- Database Migrations With Flask-Migrate
- Session, Flash, and Cookies
pip install Flask-Login
Install and Setup
pip install Flask-Loginapp/extensions.py:
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = "auth.login"
login_manager.login_message_category = "warning"app/__init__.py:
from app.extensions import db, login_manager
def create_app(config_name="development"):
...
login_manager.init_app(app)
...
return appCode explanation:
login_viewredirects unauthenticated users to the login routelogin_message_categorystyles flash message from Flask-Login
User Model With UserMixin
app/models.py:
app/__init__.py or app/models.py:
from app.models import User
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))Code explanation:
UserMixinaddsis_authenticated,is_active,get_id()for Flask-Loginuser_loaderreloads user from session ID on each request- Never store plain-text passwords—only
password_hash
Warning
Never Store Plain Passwords
Use set_password() on registration; verify with check_password() on login.
Registration View
app/auth/routes.py:
Login and Logout
Code explanation:
remember=Truesets longer-lived cookie viaREMEMBER_COOKIEsettings- Validate
nextURL to prevent open redirects—only allow relative paths
Protect Routes
from flask_login import login_required, current_user
@main_bp.route("/dashboard")
@login_required
def dashboard():
return render_template("dashboard.html", user=current_user)Template:
{% if current_user.is_authenticated %}
<p>Hello, {{ current_user.username }}</p>
<a href="{{ url_for('auth.logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('auth.login') }}">Login</a>
{% endif %}Simple Role Decorator
app/auth/decorators.py:
from functools import wraps
from flask import abort
from flask_login import current_user
def admin_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not current_user.is_authenticated or current_user.role != "admin":
abort(403)
return f(*args, **kwargs)
return decoratedUsage:
@main_bp.route("/admin")
@login_required
@admin_required
def admin_panel():
return render_template("admin.html")Flash and redirect instead of abort(403) for friendlier UX:
if current_user.role != "admin":
flash("Admin access required.", "danger")
return redirect(url_for("main.index"))Remember Me and Session Config
app.config["REMEMBER_COOKIE_DURATION"] = timedelta(days=14)
app.config["REMEMBER_COOKIE_HTTPONLY"] = True
app.config["REMEMBER_COOKIE_SECURE"] = True # HTTPS only in productionFAQ
current_user is Anonymous?
User not logged in—current_user.is_authenticated is False.
load_user returns None?
Session has stale user ID—user deleted from DB; Flask-Login treats as logged out.
Login works but @login_required loops?
login_view endpoint wrong or login route also has @login_required.
bcrypt vs werkzeug hash?
Werkzeug defaults are fine for most apps; bcrypt via passlib for stricter policies.
Multiple login backends?
Flask-Login supports one user_loader—use custom logic inside it.
OAuth (Google/GitHub)?
Use Authlib or similar—Flask-Login handles session after OAuth callback.
Compare to Spring Security?
See Spring Boot JWT and security for enterprise patterns; Flask-Login fits server-rendered sessions first.