BLOG POSTS
    MangoHost Blog / How to Add Authentication to Your App with Flask Login
How to Add Authentication to Your App with Flask Login

How to Add Authentication to Your App with Flask Login

User authentication is a foundational requirement for any web application that handles sensitive data or requires user-specific functionality. Flask-Login is a lightweight, battle-tested extension that provides session management for Flask applications, handling the heavy lifting of user sessions, login state, and authentication flows. This guide will walk you through implementing secure authentication from scratch, including user models, login forms, session management, and advanced features like “remember me” functionality.

How Flask-Login Works Under the Hood

Flask-Login manages user sessions by maintaining a user_id in the session cookie and provides decorators to protect routes that require authentication. The extension integrates seamlessly with Flask’s session management and offers callback functions to load users from your database.

The core workflow involves:

  • Login Manager initialization and configuration
  • User model implementation with required methods
  • User loader callback function
  • Login/logout route handlers
  • Route protection with decorators

Unlike heavyweight authentication frameworks, Flask-Login doesn’t dictate your database structure or password hashing strategy, giving you complete control over user management.

Step-by-Step Implementation Guide

Let’s build a complete authentication system from the ground up. First, install the required dependencies:

pip install flask flask-login flask-wtf werkzeug

Create the basic Flask application structure with Login Manager:

from flask import Flask, render_template, request, redirect, url_for, flash
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length
import sqlite3
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-change-this-in-production'

# Initialize Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = 'Please log in to access this page.'

Implement the User model with required Flask-Login methods:

class User(UserMixin):
    def __init__(self, id, username, email, password_hash):
        self.id = id
        self.username = username
        self.email = email
        self.password_hash = password_hash
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
    
    @staticmethod
    def get(user_id):
        conn = sqlite3.connect('users.db')
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
        user_data = cursor.fetchone()
        conn.close()
        
        if user_data:
            return User(user_data[0], user_data[1], user_data[2], user_data[3])
        return None
    
    @staticmethod
    def get_by_username(username):
        conn = sqlite3.connect('users.db')
        cursor = conn.cursor()
        cursor.execute('SELECT * FROM users WHERE username = ?', (username,))
        user_data = cursor.fetchone()
        conn.close()
        
        if user_data:
            return User(user_data[0], user_data[1], user_data[2], user_data[3])
        return None

Set up the user loader callback that Flask-Login uses to reload users from the session:

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

Create WTForms for login and registration:

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Remember Me')
    submit = SubmitField('Sign In')

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=4, max=20)])
    email = StringField('Email', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
    submit = SubmitField('Register')

Initialize the database and create the users table:

def init_db():
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            email TEXT UNIQUE NOT NULL,
            password_hash TEXT NOT NULL
        )
    ''')
    conn.commit()
    conn.close()

# Call this once to set up the database
if not os.path.exists('users.db'):
    init_db()

Implement the authentication routes:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    form = LoginForm()
    if form.validate_on_submit():
        user = User.get_by_username(form.username.data)
        if user and user.check_password(form.password.data):
            login_user(user, remember=form.remember_me.data)
            next_page = request.args.get('next')
            return redirect(next_page) if next_page else redirect(url_for('dashboard'))
        flash('Invalid username or password')
    
    return render_template('login.html', form=form)

@app.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    form = RegistrationForm()
    if form.validate_on_submit():
        # Check if user already exists
        if User.get_by_username(form.username.data):
            flash('Username already exists')
            return render_template('register.html', form=form)
        
        # Create new user
        password_hash = generate_password_hash(form.password.data)
        conn = sqlite3.connect('users.db')
        cursor = conn.cursor()
        cursor.execute(
            'INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)',
            (form.username.data, form.email.data, password_hash)
        )
        conn.commit()
        conn.close()
        
        flash('Registration successful')
        return redirect(url_for('login'))
    
    return render_template('register.html', form=form)

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

@app.route('/dashboard')
@login_required
def dashboard():
    return render_template('dashboard.html', user=current_user)

Real-World Examples and Use Cases

Here’s a practical example of protecting API endpoints with role-based access:

from functools import wraps

def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

@app.route('/admin/users')
@admin_required
def admin_users():
    # Admin-only functionality
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute('SELECT id, username, email FROM users')
    users = cursor.fetchall()
    conn.close()
    return render_template('admin_users.html', users=users)

For applications requiring session timeout, implement automatic logout:

from datetime import datetime, timedelta

class User(UserMixin):
    def __init__(self, id, username, email, password_hash, last_seen=None):
        self.id = id
        self.username = username
        self.email = email
        self.password_hash = password_hash
        self.last_seen = last_seen or datetime.utcnow()
    
    def is_session_expired(self, timeout_minutes=30):
        return datetime.utcnow() - self.last_seen > timedelta(minutes=timeout_minutes)

@app.before_request
def check_session_timeout():
    if current_user.is_authenticated and current_user.is_session_expired():
        logout_user()
        flash('Session expired. Please log in again.')
        return redirect(url_for('login'))

Comparison with Authentication Alternatives

Solution Complexity Features Best For Learning Curve
Flask-Login Low Session management, remember me, route protection Simple to medium applications Easy
Flask-Security Medium Registration, roles, password recovery, two-factor Feature-rich applications Medium
Flask-JWT-Extended Medium JWT tokens, refresh tokens, blacklisting API-first applications Medium
OAuth (Flask-Dance) High Third-party authentication, social login Applications requiring social auth High

Best Practices and Common Pitfalls

Security should be your top priority when implementing authentication. Always use HTTPS in production and set secure session configuration:

app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax',
    PERMANENT_SESSION_LIFETIME=timedelta(hours=1)
)

Common mistakes developers make include:

  • Storing passwords in plain text instead of using secure hashing
  • Not implementing proper session timeout mechanisms
  • Forgetting to validate user input on both client and server sides
  • Using weak secret keys or hardcoding them in source code
  • Not implementing proper error handling for authentication failures

For production deployments on VPS or dedicated servers, implement additional security measures:

# Rate limiting for login attempts
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["1000 per day", "100 per hour"]
)

@app.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
    # Login logic here
    pass

Performance considerations become important with larger user bases. Use connection pooling for database operations and consider implementing user caching:

from functools import lru_cache

@lru_cache(maxsize=1000)
def get_user_cached(user_id):
    return User.get(user_id)

# Clear cache when user data changes
def clear_user_cache(user_id):
    get_user_cached.cache_clear()

Flask-Login integrates well with popular database ORMs. Here’s an example using SQLAlchemy:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(120), nullable=False)
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

For comprehensive documentation and advanced configuration options, refer to the official Flask-Login documentation. The Flask community also maintains excellent resources at the Flask authentication patterns guide.

Testing your authentication system is crucial. Implement unit tests for your authentication logic:

import unittest
from app import app, User

class AuthTestCase(unittest.TestCase):
    def setUp(self):
        self.app = app.test_client()
        self.app.testing = True
    
    def test_login_success(self):
        response = self.app.post('/login', data={
            'username': 'testuser',
            'password': 'testpass'
        }, follow_redirects=True)
        self.assertIn(b'Dashboard', response.data)
    
    def test_login_failure(self):
        response = self.app.post('/login', data={
            'username': 'testuser',
            'password': 'wrongpass'
        })
        self.assertIn(b'Invalid username or password', response.data)

This implementation provides a solid foundation for user authentication in Flask applications, balancing security, performance, and maintainability. The modular approach allows for easy extension with additional features like email verification, password reset functionality, or integration with external authentication providers.



This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.

This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.

Leave a reply

Your email address will not be published. Required fields are marked