Skip to main content

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:

bash
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:

python
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:

python
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:

python
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:

python
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​

html
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Flask User Management{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

register.html​

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​

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​

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​

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:

python
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:

bash
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​

python
# 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​

python
# 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​

python
# 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​

python
# 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:

  1. Never store plain text passwords - Always hash passwords using strong hashing algorithms like bcrypt
  2. Implement CSRF protection - Flask-WTF provides this by default
  3. Use HTTPS - Encrypt data in transit
  4. Implement rate limiting - Prevent brute force attacks
  5. Add two-factor authentication - For additional security
  6. Use secure cookies - Set secure and HTTP-only flags
  7. Input validation - Validate all user inputs
  8. 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​

Exercises​

  1. Add a "Remember Me" functionality to the login form
  2. Implement password strength validation
  3. Create an admin dashboard to manage users
  4. Add password reset functionality via email
  5. Implement two-factor authentication
  6. 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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)