Skip to main content

Flask Login Implementation

Introduction

Authentication is a critical component of most web applications. It ensures that users can securely access their accounts and that their data remains protected. Flask, being a lightweight and flexible web framework, does not come with built-in authentication, but it offers excellent extensions like Flask-Login to handle this functionality.

In this tutorial, we'll explore how to implement user authentication in Flask applications using the Flask-Login extension. We'll cover setting up user models, managing logins, handling registrations, and protecting routes that require authentication.

What is Flask-Login?

Flask-Login is an extension that provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering users' sessions over extended periods. Flask-Login works with your existing user model and database setup, making it flexible and easy to integrate with various project structures.

Prerequisites

Before we begin, make sure you have:

  1. Basic knowledge of Flask
  2. A Flask application set up
  3. A database for user storage (we'll use SQLAlchemy in this tutorial)

Installation

First, let's install the necessary packages:

pip install flask-login flask-sqlalchemy

Step 1: Setting Up the User Model

The first step is to define a User model that is compatible with Flask-Login. This model needs to implement certain properties and methods required by Flask-Login.

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

# Initialize SQLAlchemy
db = SQLAlchemy()

class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True, nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password_hash = db.Column(db.String(200), nullable=False)

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
return check_password_hash(self.password_hash, password)

Note that our User class inherits from UserMixin, which provides default implementations for the methods Flask-Login expects:

  • is_authenticated: a property that returns True if the user has valid credentials
  • is_active: a property that returns True if the user's account is active
  • is_anonymous: a property that returns False for real users
  • get_id(): a method that returns a unique identifier for the user as a string

Step 2: Configuring Flask-Login

Now let's set up Flask-Login in our main application file:

from flask import Flask, render_template, redirect, url_for, request, flash
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from models import db, User

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key' # Replace with a real secret key
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Initialize extensions
db.init_app(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login' # Specify the login route

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

# Create database tables
with app.app_context():
db.create_all()

The login_manager.user_loader decorator registers a callback function that loads a user from the database based on their ID. This is used by Flask-Login to retrieve the current user from their session.

Step 3: Implementing Login Functionality

Now let's implement the login route and form:

@app.route('/login', methods=['GET', 'POST'])
def login():
# If user is already logged in, redirect to home page
if current_user.is_authenticated:
return redirect(url_for('index'))

if request.method == 'POST':
username = request.form['username']
password = request.form['password']

user = User.query.filter_by(username=username).first()

# Check if user exists and password is correct
if user is None or not user.check_password(password):
flash('Invalid username or password')
return redirect(url_for('login'))

# Log the user in and remember them
login_user(user, remember=request.form.get('remember_me'))

# If login is successful, redirect to the requested page or home
next_page = request.args.get('next')
if not next_page or not next_page.startswith('/'):
next_page = url_for('index')
return redirect(next_page)

return render_template('login.html')

And here's a simple login template (templates/login.html):

{% extends "base.html" %}

{% block content %}
<h1>Login</h1>
<form method="post" action="{{ url_for('login') }}">
<div>
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<input type="checkbox" id="remember_me" name="remember_me">
<label for="remember_me">Remember Me</label>
</div>
<button type="submit">Login</button>
</form>
<p>New User? <a href="{{ url_for('register') }}">Register</a></p>
{% endblock %}

Step 4: User Registration

Let's implement a registration route to add new users:

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

if request.method == 'POST':
username = request.form['username']
email = request.form['email']
password = request.form['password']

# Check if username or email already exists
if User.query.filter_by(username=username).first():
flash('Username already exists')
return redirect(url_for('register'))

if User.query.filter_by(email=email).first():
flash('Email already registered')
return redirect(url_for('register'))

# Create new user
user = User(username=username, email=email)
user.set_password(password)

# Add user to database
db.session.add(user)
db.session.commit()

flash('Congratulations, you are now registered!')
return redirect(url_for('login'))

return render_template('register.html')

And the corresponding registration template (templates/register.html):

{% extends "base.html" %}

{% block content %}
<h1>Register</h1>
<form method="post" action="{{ url_for('register') }}">
<div>
<label for="username">Username</label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<label for="password">Password</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Register</button>
</form>
<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
{% endblock %}

Step 5: Protecting Routes with @login_required

One of the main benefits of Flask-Login is the ability to protect routes that require authentication. This is done using the @login_required decorator:

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

If a user tries to access this route without being logged in, they'll be redirected to the login page (as specified in login_manager.login_view).

Step 6: Logging Out

Finally, let's implement a logout route:

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

Step 7: Creating Home and Profile Pages

Let's create simple home and profile templates to complete our application:

templates/index.html:

{% extends "base.html" %}

{% block content %}
<h1>Welcome to our Flask App</h1>
{% if current_user.is_authenticated %}
<p>Hello, {{ current_user.username }}!</p>
<p><a href="{{ url_for('profile') }}">View Profile</a></p>
<p><a href="{{ url_for('logout') }}">Logout</a></p>
{% else %}
<p>Please <a href="{{ url_for('login') }}">login</a> or <a href="{{ url_for('register') }}">register</a></p>
{% endif %}
{% endblock %}

templates/profile.html:

{% extends "base.html" %}

{% block content %}
<h1>User Profile</h1>
<p>Username: {{ user.username }}</p>
<p>Email: {{ user.email }}</p>
<p><a href="{{ url_for('index') }}">Back to Home</a></p>
{% endblock %}

templates/base.html:

<!DOCTYPE html>
<html>
<head>
<title>Flask Login Example</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.flash-messages {
color: #856404;
background-color: #fff3cd;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash-messages">
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endwith %}

{% block content %}{% endblock %}
</body>
</html>

Step 8: The Main App Route

Finally, let's add the home route to our Flask app:

@app.route('/')
def index():
return render_template('index.html')

if __name__ == '__main__':
app.run(debug=True)

Complete Application Example

Here's what our complete application looks like:

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

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key' # Replace with a real secret key
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# Initialize extensions
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'

# Define User model
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True, nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
password_hash = db.Column(db.String(200), nullable=False)

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
return check_password_hash(self.password_hash, password)

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

# Create database tables
with app.app_context():
db.create_all()

# Routes
@app.route('/')
def index():
return render_template('index.html')

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

if request.method == 'POST':
username = request.form['username']
password = request.form['password']

user = User.query.filter_by(username=username).first()

if user is None or not user.check_password(password):
flash('Invalid username or password')
return redirect(url_for('login'))

login_user(user, remember=request.form.get('remember_me'))

next_page = request.args.get('next')
if not next_page or not next_page.startswith('/'):
next_page = url_for('index')
return redirect(next_page)

return render_template('login.html')

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

if request.method == 'POST':
username = request.form['username']
email = request.form['email']
password = request.form['password']

if User.query.filter_by(username=username).first():
flash('Username already exists')
return redirect(url_for('register'))

if User.query.filter_by(email=email).first():
flash('Email already registered')
return redirect(url_for('register'))

user = User(username=username, email=email)
user.set_password(password)

db.session.add(user)
db.session.commit()

flash('Congratulations, you are now registered!')
return redirect(url_for('login'))

return render_template('register.html')

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

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

if __name__ == '__main__':
app.run(debug=True)

How It Works

  1. When a user visits the site, they can either login or register
  2. Upon successful login, Flask-Login creates a session for the user
  3. The user can then access protected routes like /profile
  4. The current_user proxy allows access to the logged-in user's data
  5. Users can logout to end their session

Best Practices for Authentication

When implementing authentication in a real-world application, consider these additional practices:

  1. Use HTTPS: Always serve your application over HTTPS to protect user credentials during transmission
  2. Password policies: Enforce strong passwords with minimum length and complexity requirements
  3. Rate limiting: Implement rate limiting to prevent brute force attacks
  4. Email verification: Add email verification when users register
  5. Password reset: Provide a secure way for users to reset forgotten passwords
  6. Two-factor authentication: For sensitive applications, consider adding 2FA
  7. CSRF protection: Flask has built-in CSRF protection; make sure it's enabled

Extending the Login System

Here are some ways to enhance your authentication system:

  1. Role-based access control: Add roles to users (admin, standard user, etc.)
class User(UserMixin, db.Model):
# ... existing fields ...
role = db.Column(db.String(20), default='user')

# Role checking decorator
def admin_required(func):
@wraps(func)
def decorated_view(*args, **kwargs):
if not current_user.is_authenticated or current_user.role != 'admin':
abort(403) # Forbidden
return func(*args, **kwargs)
return decorated_view
  1. Social login: Integrate with OAuth providers (Google, Facebook, GitHub)
  2. Remember me functionality: Flask-Login already includes this, but you can customize the duration
  3. Account lockout: Lock accounts after multiple failed login attempts

Summary

In this tutorial, we've learned:

  1. How to set up Flask-Login in a Flask application
  2. Creating a User model with authentication methods
  3. Implementing login, registration, and logout functionality
  4. Protecting routes that require authentication
  5. Accessing the current user's information in templates
  6. Best practices for secure authentication

Flask-Login provides a simple but powerful system for handling user authentication. By following the steps outlined in this tutorial, you can implement a secure and user-friendly authentication system in your Flask applications.

Exercises

To reinforce your learning, try these exercises:

  1. Add password confirmation to the registration form
  2. Implement "remember me" functionality that keeps users logged in across browser sessions
  3. Add a profile editing page where users can update their information
  4. Create an admin role with access to a special admin dashboard
  5. Add email verification using a token system
  6. Implement a "forgot password" feature

Additional Resources

Happy coding!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)