Flask User Management
User management is a crucial component of most web applications. It encompasses features like registration, login, profile management, and role-based access control that ensure your application can maintain a secure relationship with its users. In this guide, we'll learn how to implement a comprehensive user management system in a Flask application.
Introduction
Effective user management allows your Flask application to:
- Register new users securely
- Authenticate users with login/logout capabilities
- Manage user profiles and personal information
- Control access to different parts of your application
- Track user activities
We'll be building a user management system step-by-step, using Flask-Login, Flask-WTF for forms, and SQLAlchemy for database operations.
Prerequisites
Before starting, make sure you have:
- Basic knowledge of Python and Flask
- Flask installed in your environment
- Understanding of database concepts
- Familiarity with HTML templates
Setting Up Your Flask Project
Let's start by creating a basic structure for our Flask project:
mkdir flask_user_management
cd flask_user_management
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install flask flask-sqlalchemy flask-login flask-wtf email_validator
Now let's create our basic application structure:
flask_user_management/
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ └── templates/
│ ├── base.html
│ ├── login.html
│ ├── register.html
│ └── profile.html
└── run.py
Creating the User Model
Let's start by defining our User model in models.py
:
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
db = SQLAlchemy()
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
email = db.Column(db.String(120), unique=True, index=True)
password_hash = db.Column(db.String(128))
is_active = db.Column(db.Boolean, default=True)
is_admin = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
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)
def __repr__(self):
return f'<User {self.username}>'
The UserMixin
class provides default implementations for the methods that Flask-Login expects user objects to have. We also have methods for securely setting and checking passwords.
Initializing the Flask Application
Now let's set up our Flask app in __init__.py
:
from flask import Flask
from flask_login import LoginManager
from .models import db, User
login_manager = LoginManager()
login_manager.login_view = 'login'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
login_manager.init_app(app)
from .routes import main
app.register_blueprint(main)
return app
Here we're setting up Flask-Login, configuring our database, and registering our blueprint for routes.
Creating Forms
Let's create forms for user registration and login in a new file called forms.py
:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
from .models import User
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=64)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('Please use a different email address.')
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In')
These forms will handle the validation of user input when registering or logging in.
Creating Routes
Now, let's implement our routes in routes.py
:
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from .models import db, User
from .forms import RegistrationForm, LoginForm
main = Blueprint('main', __name__)
@main.route('/')
def index():
return render_template('index.html')
@main.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url_for('main.login'))
return render_template('register.html', form=form)
@main.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid email or password')
return redirect(url_for('main.login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or not next_page.startswith('/'):
next_page = url_for('main.profile')
return redirect(next_page)
return render_template('login.html', form=form)
@main.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('main.index'))
@main.route('/profile')
@login_required
def profile():
return render_template('profile.html')
HTML Templates
Let's create our HTML templates:
base.html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Flask User Management{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="{{ url_for('main.index') }}">Flask App</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
{% if current_user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.profile') }}">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.logout') }}">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.login') }}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('main.register') }}">Register</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
register.html
{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6 offset-md-3">
<h2>Register</h2>
<form method="POST" action="{{ url_for('main.register') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control") }}
{% for error in form.username.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% for error in form.password.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.confirm_password.label(class="form-label") }}
{{ form.confirm_password(class="form-control") }}
{% for error in form.confirm_password.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<p>Already have an account? <a href="{{ url_for('main.login') }}">Login here</a>.</p>
</div>
</div>
{% endblock %}
login.html
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6 offset-md-3">
<h2>Login</h2>
<form method="POST" action="{{ url_for('main.login') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control") }}
{% for error in form.password.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
</div>
<div class="mb-3 form-check">
{{ form.remember_me(class="form-check-input") }}
{{ form.remember_me.label(class="form-check-label") }}
</div>
<div class="mb-3">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<p>New User? <a href="{{ url_for('main.register') }}">Register here</a>.</p>
</div>
</div>
{% endblock %}
profile.html
{% extends "base.html" %}
{% block title %}Profile{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="card">
<div class="card-header">
<h3>User Profile</h3>
</div>
<div class="card-body">
<h5 class="card-title">Welcome, {{ current_user.username }}!</h5>
<p class="card-text">Email: {{ current_user.email }}</p>
<p class="card-text">Member since: {{ current_user.created_at.strftime('%B %d, %Y') }}</p>
{% if current_user.is_admin %}
<div class="alert alert-info">
You have admin privileges.
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
index.html
{% extends "base.html" %}
{% block content %}
<div class="jumbotron">
<h1 class="display-4">Welcome to Flask User Management</h1>
<p class="lead">A sample application demonstrating user authentication and management with Flask.</p>
<hr class="my-4">
{% if current_user.is_authenticated %}
<p>You are logged in as {{ current_user.username }}.</p>
<a class="btn btn-primary btn-lg" href="{{ url_for('main.profile') }}" role="button">View Profile</a>
{% else %}
<p>Please login to access your profile.</p>
<a class="btn btn-primary btn-lg" href="{{ url_for('main.login') }}" role="button">Login</a>
<a class="btn btn-secondary btn-lg" href="{{ url_for('main.register') }}" role="button">Register</a>
{% endif %}
</div>
{% endblock %}
Setting Up The Application
Finally, let's create our run.py
file to run our application:
from app import create_app, db
app = create_app()
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True)
Running the Application
Now we can run our Flask application:
python run.py
The application will start on http://127.0.0.1:5000/, and you should be able to register a new user, log in, view your profile, and log out.
Adding Advanced Features
Once you have the basic user management system in place, you can enhance it with more advanced features:
1. Password Reset Functionality
# Additional route in routes.py
@main.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = ResetPasswordRequestForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
# Send password reset email
send_password_reset_email(user)
flash('Check your email for instructions to reset your password')
return redirect(url_for('main.login'))
return render_template('reset_password_request.html', form=form)
2. Role-Based Access Control
# Enhanced User model in models.py
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.Integer)
users = db.relationship('User', backref='role', lazy='dynamic')
def __repr__(self):
return f'<Role {self.name}>'
# Update User model
class User(UserMixin, db.Model):
# Existing fields...
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
def has_permission(self, permission):
return self.role is not None and (self.role.permissions & permission) == permission
3. Account Activation by Email
# Add to User model
class User(UserMixin, db.Model):
# Existing fields...
confirmed = db.Column(db.Boolean, default=False)
def generate_confirmation_token(self):
# Generate token for email confirmation
...
def confirm(self, token):
# Verify token and set confirmed = True
...
# Add confirmation route
@main.route('/confirm/<token>')
@login_required
def confirm(token):
if current_user.confirmed:
return redirect(url_for('main.index'))
if current_user.confirm(token):
db.session.commit()
flash('Your account has been confirmed. Thanks!')
else:
flash('The confirmation link is invalid or has expired.')
return redirect(url_for('main.index'))
4. User Profile Editing
# Add a form for profile editing
class EditProfileForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=64)])
about_me = TextAreaField('About me', validators=[Length(min=0, max=140)])
submit = SubmitField('Submit')
# Add a route for profile editing
@main.route('/edit_profile', methods=['GET', 'POST'])
@login_required
def edit_profile():
form = EditProfileForm()
if form.validate_on_submit():
current_user.username = form.username.data
current_user.about_me = form.about_me.data
db.session.commit()
flash('Your profile has been updated.')
return redirect(url_for('main.profile'))
elif request.method == 'GET':
form.username.data = current_user.username
form.about_me.data = current_user.about_me
return render_template('edit_profile.html', form=form)
Security Best Practices
When implementing user management, always follow these security best practices:
- Never store plain text passwords - Always hash passwords using strong hashing algorithms like bcrypt
- Implement CSRF protection - Flask-WTF provides this by default
- Use HTTPS - Encrypt data in transit
- Implement rate limiting - Prevent brute force attacks
- Add two-factor authentication - For additional security
- Use secure cookies - Set secure and HTTP-only flags
- Input validation - Validate all user inputs
- Secure password recovery - Implement secure recovery mechanisms
Summary
In this tutorial, we've built a comprehensive user management system for a Flask application that includes:
- User registration and authentication
- Secure password handling
- Login and logout functionality
- Protected routes with Flask-Login
- User profile management
- Role-based access control concepts
This system provides a foundation that you can expand with additional features like email verification, password reset, social authentication, and more advanced user roles.
Additional Resources
- Flask Documentation
- Flask-Login Documentation
- Flask-WTF Documentation
- Flask-SQLAlchemy Documentation
- OWASP Authentication Cheat Sheet
Exercises
- Add a "Remember Me" functionality to the login form
- Implement password strength validation
- Create an admin dashboard to manage users
- Add password reset functionality via email
- Implement two-factor authentication
- Create a user profile page with the ability to upload a profile picture
By completing these exercises, you'll have a robust user management system ready for real-world applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)