Flask-Login — User registration, login, logout

In this chapter, you will learn to implement session user authentication with Flask-Login, which is the most important security safeguard in Flask web development.


What is Flask-Login?

HTTP requests in web applications are stateless in nature — the server does not automatically remember who you are.

Flask-LoginThroughSessionThe mechanism maintains user state: after login, the server stores the user ID in the Session, and subsequent requests automatically identify the current user.


Installation and Initialization

(venv) $ pip install flask-login

Example

# File path: app.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SECRET_KEY'] = 'your-secret-key-here'   # Session encryption key (use environment variable in production)

db = SQLAlchemy(app)

# Initialize LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login'     # Redirect unauthenticated users to login page
login_manager.login_message = 'Please log in first before accessing.'

SECRET_KEYIt is the cornerstone of the Flask security system — session encryption, CSRF tokens, and flash messages all depend on it. In production environments, it must be read from environment variables and must not be hard-coded.


Define the User model

Flask-Login requires the User model to implementUserMixinSeveral methods provided.

Example

# File path: models.py, new additions
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin, db.Model):
    """User model"""
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(256), nullable=False)

    def set_password(self, password):
        """Hash and encrypt the password (never store plaintext passwords)"""
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        """Verify whether the password matches"""
        return check_password_hash(self.password_hash, password)

    def __repr__(self):
        return f'<User {self.username}>'

# Define user_loader: Flask-Login uses this function to restore the user object from the session
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

UserMixin automatically injects the following attributes and methods into the User model:

Attributes/MethodsDescription
is_authenticatedWhether the user is logged in
is_activeWhether the user is active
is_anonymousWhether it is an anonymous user (not logged in)
get_id()Get the user primary key ID

Generate migration and execute:

(venv) $ flask db migrate -m "新增 User 模型"
(venv) $ flask db upgrade

Create an authentication Blueprint

Example

# File path: app/blueprints/auth.py
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required, current_user
from app.models import User, db

auth_bp = Blueprint('auth', __name__)

@auth_bp.route("/register", methods=['GET', 'POST'])
def register():
    """User registration"""
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        email = request.form.get('email', '').strip()
        password = request.form.get('password', '')

        # Validate required fields
        if not username or not email or not password:
            flash('All fields must be filled in.', 'error')
            return render_template('register.html')

        # Check if username and email already exist
        if User.query.filter_by(username=username).first():
            flash('Username is already taken.', 'error')
            return render_template('register.html')
        if User.query.filter_by(email=email).first():
            flash('Email is already registered.', 'error')
            return render_template('register.html')

        # Create a user
        user = User(username=username, email=email)
        user.set_password(password)    # Hash and store the password
        db.session.add(user)
        db.session.commit()

        login_user(user)               # Automatically log in after registration
        flash(f'Registration successful, welcome, {username}!', 'success')
        return redirect(url_for('main.index'))

    return render_template('register.html')


@auth_bp.route("/login", methods=['GET', 'POST'])
def login():
    """User login"""
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        password = request.form.get('password', '')

        # user_loader looks up the user from the database
        user = User.query.filter_by(username=username).first()

        # Verify the password (the password itself will not be recorded in logs)
        if user is None or not user.check_password(password):
            flash(Incorrect username or password., 'error')
            return render_template('login.html')

        login_user(user, remember=request.form.get('remember'))
        flash(f'Welcome back, {username}!', 'success')

        # After login, redirect to the page visited before login (the next parameter in the URL)
        next_page = request.args.get('next')
        return redirect(next_page or url_for('main.index'))

    return render_template('login.html')


@auth_bp.route("/logout")
@login_required       # Only logged-in users can log out
def logout():
    logout_user()
    flash('Logged out securely.', 'info')
    return redirect(url_for('main.index'))

References needed in the App design

Example

# File path: register the auth Blueprint in app.py
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp)

Never store passwords in plain text!generate_password_hash()Use the PBKDF2 algorithm to perform salted hashing on passwords. Even if the database is leaked, attackers cannot reverse-engineer the password. When verifying the password, usecheck_password_hash()。


Create login and registration templates

Example

<!-- File path: app/templates/login.html -->
{% extends 'base.html' %}

{% block title %}Login - EXAMPLE Blog{% endblock %}

{% block content %}
<div class="auth-form">
    <h2>Log in</h2>
    <form method="post">
        <div class="form-group">
            <label>Username</label>
            <input type="text" name="username" required>
        </div>
        <div class="form-group">
            <label>Password</label>
            <input type="password" name="password" required>
        </div>
        <div class="form-group">
            <label>
                <input type="checkbox" name="remember">Remember me
            </label>
        </div>
        <button type="submit" class="btn-submit">Log in</button>
    </form>
    <p class="form-footer">
No account yet?<a href="{{ url_for('auth.register') }}">Register now</a>
    </p>
</div>
{% endblock %}

Update navigation bar to show login status

Example

<!-- File path: navigation bar section in app/templates/base.html -->
<nav>
    <a href="/">Home</a>
    {% if current_user.is_authenticated %}
        <span class="user-name">{{ current_user.username }}</span>
        <a href="{{ url_for('auth.logout') }}">Logout</a>
    {% else %}
        <a href="{{ url_for('auth.login') }}">Log in</a>
        <a href="{{ url_for('auth.register') }}">Register</a>
    {% endif %}
</nav>

current_userIt is a proxy object automatically injected by Flask-Login, directly usable in all templates and view functions. When logged in, it is a User object; when not logged in, it is an AnonymousUserMixin instance.


Chapter summary

In this chapter, you integrated Flask-Login Session authentication: UserMixin + user_loader combined the user model, login_user/logout_user managed the Session, werkzeug hashed passwords, @login_required protected views, and current_user in templates checked the status.

Now the blog has a complete user registration, login, and logout flow.

other extensions