Flask User Authentication: Implementing Permission Control with Flask-Login

In web applications, user authentication (verifying user identity) and permission control (restricting actions based on user roles) are core functionalities. As a lightweight web framework, Flask can easily implement these features using the Flask-Login extension. This article will guide beginners through the simplest steps to understand user login, authentication state maintenance, and basic permission control with Flask-Login.

1. Preparation: Install Required Libraries

First, install Flask and Flask-Login. If storing user data, also install Flask-SQLAlchemy (database ORM) and Werkzeug (password hashing). Run in the terminal:

pip install flask flask-login flask-sqlalchemy werkzeug

2. Core Concept: Role of Flask-Login

Flask-Login primarily addresses two issues:
1. Session Management: Automatically maintains user login state (e.g., “Remember Me” functionality, automatic logout on timeout).
2. Permission Verification: Controls route access using decorators (e.g., restricting access to logged-in users only).

3. Step 1: Configure the App and User Model

3.1 Initialize the App and Database

Create app.py and initialize the Flask app and database:

from flask import Flask, render_template, redirect, url_for, request, flash
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash

# Initialize Flask app
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'  # Use a random string in production
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'  # SQLite database
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # Disable unnecessary tracking

# Initialize database and LoginManager
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'  # Redirect unauthenticated users here

3.2 Define the User Model

Create a User class inheriting from UserMixin (implements default Flask-Login methods) to store user data:

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)  # User ID
    username = db.Column(db.String(80), unique=True, nullable=False)  # Username
    password_hash = db.Column(db.String(120), nullable=False)  # Hashed password (not plaintext)

    # Hash password before storage
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    # Verify password during login
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

3.3 Create Database Tables

In the terminal, use Python to create database tables:

>>> from app import app, db
>>> with app.app_context():
...     db.create_all()  # Creates table structures

4. Step 2: Configure User Loading and Authentication

4.1 User Loading Function

Flask-Login requires a function to load users from the session. Register it with @login_manager.user_loader:

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))  # Fetch user by ID from the database
  • Purpose: After login, Flask-Login uses this function to load user data into the session.

5. Step 3: Implement Login and Logout

5.1 Login Page and Form

Create templates/login.html for the login form:

<!-- templates/login.html -->
<h1>Login</h1>
{% if error %}
    <p style="color: red;">{{ error }}</p>
{% endif %}
<form method="POST">
    <input type="text" name="username" placeholder="Username" required><br><br>
    <input type="password" name="password" placeholder="Password" required><br><br>
    <button type="submit">Login</button>
</form>

5.2 Login Route

Add login logic to app.py:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()

        # Verify credentials
        if user and user.check_password(password):
            login_user(user)  # Maintain login state (session)
            return redirect(url_for('dashboard'))  # Redirect to dashboard
        else:
            return render_template('login.html', error='Invalid username or password')

    return render_template('login.html')  # Show form for GET requests

5.3 Logout Route

Use logout_user() to clear the session:

@app.route('/logout')
@login_required  # Only accessible to logged-in users
def logout():
    logout_user()  # Clear session
    return redirect(url_for('login'))  # Redirect to login page

6. Step 4: Permission Control

6.1 Protect Routes (Authenticated Users Only)

Use @login_required to restrict access to logged-in users:

@app.route('/dashboard')
@login_required  # Redirects unauthenticated users to login_view
def dashboard():
    return f"Welcome back, {current_user.username}!"  # current_user = active user

6.2 Role-Based Access Control (Advanced)

Add a role field to the user model for granular permissions:

class User(UserMixin, db.Model):
    # Existing fields...
    role = db.Column(db.String(20), default='user')  # e.g., 'user' or 'admin'

# Restrict admin-only routes
@app.route('/admin')
@login_required
def admin_panel():
    if current_user.role != 'admin':  # Check user role
        flash('You do not have admin access!')
        return redirect(url_for('dashboard'))
    return "Admin Dashboard"

7. Key Notes

  1. Password Security: Always hash passwords with Werkzeug (never store plaintext passwords):
   user.set_password(password)  # Hash before storage
   user.check_password(password)  # Verify during login
  1. Session Security: Use a random, complex SECRET_KEY (store in environment variables in production).
  2. User Loading: The @login_manager.user_loader function must return the correct user object.
  3. Current User: current_user provides access to the active user’s data (e.g., current_user.username).

8. Summary

With Flask-Login, we implemented:
- Automatic session management for login state.
- Route protection via @login_required.
- Basic role-based access control (via user roles).

Next steps: Add “Remember Me” functionality, password reset, or OAuth for third-party logins.

Full Code Example

app.py

# [All previous code except imports]

if __name__ == '__main__':
    with app.app_context():
        db.create_all()  # Create tables on first run
    app.run(debug=True)

templates/login.html

<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
    <h1>Login</h1>
    {% if error %}
        <p style="color: red;">{{ error }}</p>
    {% endif %}
    <form method="POST">
        <input type="text" name="username" placeholder="Username" required><br><br>
        <input type="password" name="password" placeholder="Password" required><br><br>
        <button type="submit">Login</button>
    </form>
</body>
</html>

Run python app.py and visit http://localhost:5000/login to test the login flow!

Xiaoye