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.

Leave a Comment

Your email address will not be published. Required fields are marked *