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:
- Basic knowledge of Flask
- A Flask application set up
- 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 returnsTrue
if the user has valid credentialsis_active
: a property that returnsTrue
if the user's account is activeis_anonymous
: a property that returnsFalse
for real usersget_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
- When a user visits the site, they can either login or register
- Upon successful login, Flask-Login creates a session for the user
- The user can then access protected routes like
/profile
- The
current_user
proxy allows access to the logged-in user's data - Users can logout to end their session
Best Practices for Authentication
When implementing authentication in a real-world application, consider these additional practices:
- Use HTTPS: Always serve your application over HTTPS to protect user credentials during transmission
- Password policies: Enforce strong passwords with minimum length and complexity requirements
- Rate limiting: Implement rate limiting to prevent brute force attacks
- Email verification: Add email verification when users register
- Password reset: Provide a secure way for users to reset forgotten passwords
- Two-factor authentication: For sensitive applications, consider adding 2FA
- 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:
- 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
- Social login: Integrate with OAuth providers (Google, Facebook, GitHub)
- Remember me functionality: Flask-Login already includes this, but you can customize the duration
- Account lockout: Lock accounts after multiple failed login attempts
Summary
In this tutorial, we've learned:
- How to set up Flask-Login in a Flask application
- Creating a User model with authentication methods
- Implementing login, registration, and logout functionality
- Protecting routes that require authentication
- Accessing the current user's information in templates
- 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:
- Add password confirmation to the registration form
- Implement "remember me" functionality that keeps users logged in across browser sessions
- Add a profile editing page where users can update their information
- Create an admin role with access to a special admin dashboard
- Add email verification using a token system
- Implement a "forgot password" feature
Additional Resources
- Flask-Login Documentation
- Flask Web Development with Python Tutorial
- OWASP Authentication Best Practices
- Flask Security for more advanced security features
Happy coding!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)