Flask Login & Signup with Sessions and Dashboards
Authentication is the backbone of every real web app. In this post we build it from scratch using only Flask and the standard library — no flask-login, no JWT, just good old sessions.
The User Table
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
role TEXT DEFAULT 'user',
created_at TEXT DEFAULT (datetime('now'))
);
Secure Password Hashing
Never store plain text passwords. Use PBKDF2:
import hashlib, os
def hash_password(password: str) -> str:
salt = os.urandom(32)
key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 310_000)
return salt.hex() + ':' + key.hex()
def verify_password(stored: str, candidate: str) -> bool:
salt_hex, key_hex = stored.split(':')
salt = bytes.fromhex(salt_hex)
key = hashlib.pbkdf2_hmac('sha256', candidate.encode(), salt, 310_000)
return key.hex() == key_hex
Signup Route
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
username = request.form['username'].strip()
email = request.form['email'].strip().lower()
password = request.form['password']
if len(password) < 8:
flash('Password must be at least 8 characters.', 'error')
return redirect(url_for('signup'))
db = get_db()
if db.execute('SELECT id FROM users WHERE email=?', (email,)).fetchone():
flash('Email already registered.', 'error')
return redirect(url_for('signup'))
db.execute(
'INSERT INTO users (username, email, password) VALUES (?,?,?)',
(username, email, hash_password(password))
)
db.commit()
flash('Account created! Please log in.', 'success')
return redirect(url_for('login'))
return render_template('signup.html')
Login & Session
from flask import session
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form['email'].strip().lower()
password = request.form['password']
user = get_db().execute(
'SELECT * FROM users WHERE email=?', (email,)
).fetchone()
if user and verify_password(user['password'], password):
session.permanent = True
session['user_id'] = user['id']
session['username'] = user['username']
session['user_role'] = user['role']
return redirect(url_for('dashboard'))
flash('Invalid credentials.', 'error')
return render_template('login.html')
@app.route('/logout')
def logout():
session.clear()
return redirect(url_for('login'))
Protecting Routes
from functools import wraps
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if 'user_id' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated
def admin_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if session.get('user_role') != 'admin':
abort(403)
return f(*args, **kwargs)
return decorated
@app.route('/dashboard')
@login_required
def dashboard():
user = get_db().execute(
'SELECT * FROM users WHERE id=?', (session['user_id'],)
).fetchone()
return render_template('dashboard.html', user=user)
The Dashboard Template
{% extends "base.html" %}
{% block content %}
<h1>Welcome back, {{ user.username }}!</h1>
<p>Role: <strong>{{ user.role }}</strong></p>
<a href="{{ url_for('logout') }}">Log out</a>
{% endblock %}
That's a complete, production-ready auth system in under 80 lines of Python.
1 Comment
Join the conversation