Flask Login System
A login system identifies users and remembers who they are across requests. This topic builds a complete login and logout system from scratch using sessions, password hashing, and route protection — without any third-party login library.
The Login Flow
User fills login form
│
POST /login
│
Flask reads username + password from form
│
Queries database for user with that username
│
User found?
├── NO → flash error, show form again
└── YES → check password hash
│
Password matches?
├── NO → flash error, show form again
└── YES → session['user_id'] = user.id
redirect to dashboard
User Model with Password Hashing
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(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(256), 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)Passwords are never stored as plain text. generate_password_hash() creates a secure one-way hash. check_password_hash() verifies a plain password against the stored hash without reversing it.
Registration Route
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
existing = User.query.filter_by(username=username).first()
if existing:
flash('Username already taken.', 'error')
return redirect(url_for('register'))
user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
flash('Account created! Please log in.', 'success')
return redirect(url_for('login'))
return render_template('register.html')Login Route
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
session['user_id'] = user.id
session['username'] = user.username
flash('Logged in successfully.', 'success')
return redirect(url_for('dashboard'))
flash('Invalid username or password.', 'error')
return render_template('login.html')Logout Route
@app.route('/logout')
def logout():
session.clear()
flash('You have been logged out.', 'info')
return redirect(url_for('home'))Protecting Routes
Create a decorator that blocks unauthenticated users from protected pages:
from functools import wraps
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user_id' not in session:
flash('Please log in to access this page.', 'warning')
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated
@app.route('/dashboard')
@login_required
def dashboard():
user = User.query.get(session['user_id'])
return render_template('dashboard.html', user=user)Flash Messages in Templates
Flask's flash() stores a one-time message in the session. Display it in templates with get_flashed_messages():
{# In base.html #}
{% with messages = get_flashed_messages(with_categories=True) %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}Showing the Current User in Navigation
{# In base.html nav #}
{% if session.get('username') %}
<span>Hello, {{ session['username'] }}</span>
<a href="{{ url_for('logout') }}">Log Out</a>
{% else %}
<a href="{{ url_for('login') }}">Log In</a>
<a href="{{ url_for('register') }}">Register</a>
{% endif %}Summary
A from-scratch Flask login system stores hashed passwords in the database, verifies them on login, and saves the user's ID in the session. The login_required decorator protects any route by checking the session before running the view function. Flash messages provide one-time feedback after login, logout, and registration. This foundation is solid for small applications and teaches the exact mechanics that libraries like Flask-Login automate.
