Flask Authentication


This entry is part 3 of 3 in the series Flask

Are you working on a Flask web application that requires authentication by users? Do you want to follow best practices. Users will need to first register with a valid email and password. Next, they will receive an email with a link inside. When they click the link, they become validated. Once their account is validated they can log in with their password and have special privilages.

Later, after setting this up, you can implement the ability to change the password.

See below for a special section called How it all Connects.

Typical Flask Auth Project File Structure

This is provided by ChatGPT.

/your_flask_project/
│
├── app/
│   ├── __init__.py            # Initializes the app and extensions
│   ├── models.py              # User model (and other DB models)
│   ├── routes.py              # Flask route functions (views)
│   ├── forms.py               # WTForms classes for login, register, etc.
│   ├── email.py               # Utility functions for sending emails
│   ├── tokens.py              # Token generation/verification (Itsdangerous)
│   ├── templates/
│   │   ├── base.html          # Base layout for all templates
│   │   ├── login.html         # Login form view
│   │   ├── register.html      # Registration form view
│   │   ├── confirm_email.html # Email confirmation message
│   │   └── ...                # Other templates
│   └── static/                # Optional CSS or JS
│
├── migrations/                # Auto-generated by Flask-Migrate (optional)
│
├── venv/                      # Virtual environment (not included in Git)
│
├── config.py                  # Configuration variables (mail server, secret key, etc.)
├── flask_app.py               # Entry point (runs the app)
├── requirements.txt           # List of Python packages
└── README.md                  # Project overview

File Descriptions

Below are some basic highlights of the files in the project. When you run the project, you must first start the server. It will provide you web pages in your browser at http://localhost:5000/ when you are in development. If you don’t start the server and let it know where the entry point is, your bros=wser will display a message saying it can’t reach the page and in Edge it says “localhost refused to connect.” In Chrome it says “This site can’t be reached”. In Windows, you start your project by opening a command prompt and navigating to the project’s root folder. You then set the environment variable. Next you enter flask run. Let’s start with the entry point. There are several files to create, after you create your project folder. I use Notepad++ to create the files and then change the file extention to py or html or whatever is needed. There are ten files in this list.

flask_app.py

from app import create_app
app = create_app()

app/__init__.py

Initializes the Flask app, sets config, and registers extensions like:

  • Flask-Login
  • Flask-Mail
  • Flask-Migrate
  • Flask-WTF
  • SQLAlchemy

Initializes means to “turn on the engine”. You now have a running vehicle (your app) that can be customized and driven. To set config, or configuration is to tell Flask how the app should behave by setting various options (like security keys, database path, mail server settings, etc.). You’re setting the dials and preferences: turning on the headlights, adjusting the seat, choosing radio stations — it’s still the same vehicle, but now it knows how to act. To register extension is to connect additional tools or features to your app — things like login systems, email sending, or database access. Here are two code examples: db.init_app(app) and login_manager.init_app(app). You’re plugging accessories into your vehicle: GPS, backup camera, or a phone charger — they don’t come with the car, but now they’re part of it and ready to use. Initializing is like starting the app, configuring is setting its behavior, and registering extensions is adding extra tools so the app can do more.

It also registers your Blueprints or route modules.

app/models.py

Defines your User model, including:

  • ID, username, email, password_hash
  • is_authenticated, get_id(), etc. for Flask-Login
  • generate_confirmation_token() or check_password() methods

What is models.py in Flask? A Simple Definition: models.py is where we define the structure of the database using Python classes.
Each class = table, and each class attribute = column.

  • Model: A Python class that represents a database table
  • Field (Column): Each variable inside the class becomes a column
  • db.Model: A base class from SQLAlchemy that tells Flask: “This is a database model”
  • UserMixin: Adds helpful methods for login (like is_authenticated, get_id())

By using models, you don’t have to write raw SQL like INSERT INTO Users … You can do things like:

new_user = User(username='mike', email='mike@example.com')
db.session.add(new_user)
db.session.commit()

That one object becomes a row in the database!

app/forms.py

Notice that we need to create a new folder called apps. Holds your form classes using Flask-WTF, such as:

  • LoginForm
  • RegistrationForm
  • ResetPasswordForm

In Flask, this file typically contains Python classes that define your web forms — like login, registration, password reset, etc. Flask-WTF is an extension that makes it easy to build and validate forms in Flask using Python code. It’s a wrapper around WTForms, which is a general Python library for web forms. Why Use Flask-WTF? Without Flask-WTF you’d have to: Manually handle form input in HTML, Write your own validation logic (e.g., “is this email valid?”), Manually redisplay error messages, Sanitize inputs against injection
With Flask-WTF: You define the form in Python, You get auto-validation and error messages You get CSRF protection built in (important for security). Here below is an example of a login form. It would be in your porms.py file.

# app/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email
class LoginForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Log In')

This form: Has an email field, A password field, A submit button, Validates that all fields are filled out, & Validates that the email has the right format. Below is a template example.

<form method="POST">
  {{ form.hidden_tag() }}  <!-- CSRF token -->
  {{ form.email.label }} {{ form.email() }}
  {{ form.password.label }} {{ form.password() }}
  {{ form.submit() }}
</form>

app/routes.py

Holds your app’s main view logic (routes). routes.py is the beating heart of the app where user actions (like clicking “Log In” or submitting a form) are actually handled.. They also uses @login_required decorators from Flask-Login.

  • /login
  • /logout
  • /register
  • /confirm/
  • /dashboard (protected route)

routes.py defines the URLs (routes) in your app and connects them to Python functions called view functions. In an authentication system, it handles things like: Showing the login form, Processing form submissions, Logging users in and out, Handling email confirmation, password resets, etc.

How It Works (Step by Step)

  • User visits /login → Flask shows the login form.
  • User submits the form → Flask checks if the input is valid.
  • If valid → Flask logs the user in and redirects to dashboard.
  • If invalid → Flask shows the form again with errors.

routes.py is the logic layer of your Flask app. It connects each URL (like /login, /logout) to a function that handles what to show and what to do.
In authentication, this means checking login credentials, managing sessions, and protecting pages from unauthorized access.

app/email.py

Handles sending emails (e.g., registration confirmation or password reset).

  • Connects to mail server using Flask-Mail
  • Sends templated HTML emails

email.py contains functions for sending emails from your Flask app — like welcome emails, confirmation links, or password reset instructions. It works in partnership with: Flask-Mail (to send emails), Itsdangerous (to generate secure tokens), and Jinja templates (to format the email content). email.py makes it easy to send confirmation or reset emails from your Flask app. It uses Flask-Mail to connect to an email server, Jinja templates to format messages, and a reusable send_email() function to keep your code clean. This is how your app communicates important info — securely and professionally — with users.

app/tokens.py

Uses itsdangerous to generate and verify tokens. Email confirmation tokens and Password reset tokens. tokens.py contains functions to generate and verify secure tokens used in email confirmation and password reset links. These tokens are: Secure, Time-sensitive, Unique to each user, and they’re generated using the Itsdangerous library — a Flask-compatible package designed exactly for this purpose. tokens.py helps keep your users — and your app — secure. It uses the Itsdangerous library to create tokens you can safely include in URLs, like in confirmation or password reset links. These tokens expire after a set time and are tamper-resistant. Together with email.py, it gives you a secure and professional email-based flow.

app/templates/*.html

The frontend part that uses HTML. Each page (login, register, etc.) inherits from base.html. It Contains forms rendered with WTForms and Confirmation messages, alerts, or dashboard content.

templates/confirm_email.html

confirm_email.html is an HTML email template used to send the user a clickable link to confirm their account. This template is rendered using render_template() in email.py and sent to the user’s inbox. There are two versions: HTML and plain text. Why both versions? Some email clients (especially older ones) don’t support HTML. Flask-Mail allows you to send both versions to ensure compatibility. confirm_email.html is a Jinja2 template that produces a beautiful, safe, and functional email confirmation message. It dynamically includes the user’s name and a one-time-use link to activate their account. By sending both an HTML and plain text version, your app remains polished and accessible.

Login.html and Register.html

login.html. A simple form template that allows returning users to log in using their email and password. It uses Flask-WTF to display form fields, show validation errors, and includes a CSRF token for security. register.html. A form for new users to sign up by providing details like username, email, and password. It validates input, confirms passwords match, and triggers an email confirmation process via a secure token link.

base.html

You will place your base.html file in your templates folder. A shared layout template that defines the overall structure of every page — like the header, navigation bar, footer, and basic HTML skeleton. Other templates like login.html, register.html, and dashboard.html extend this base so they only need to define the content that changes. Here is a list of typical features.

  • HTML5 with title and CSS links
  • A navbar (often with login/logout buttons)
  • A block for flash messages
  • A {% block content %} placeholder where page-specific content gets injected
  • Optional support for Bootstrap or Tailwind

🔁 How It’s Used: In login.html, you’d see:

{% extends "base.html" %}
{% block content %}
    <!-- Login form here -->
{% endblock %}

Why It Matters: Promotes consistency across pages, Makes your templates easier to manage and update, Centralizes layout logic (update it once, and all pages follow). Here is an example.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="Content-Language" content="en" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>{% block title %}Flask Auth Project{% endblock %}</title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 text-gray-800">

  <!-- Navbar -->
  <nav class="bg-blue-700 text-white p-4 shadow-md">
    <div class="container mx-auto flex justify-between items-center">
      <a href="/" class="text-xl font-semibold">My App</a>
      <div>
        {% if current_user.is_authenticated %}
          <span class="mr-4">Welcome, {{ current_user.username }}</span>
          <a href="{{ url_for('logout') }}" class="underline">Logout</a>
        {% else %}
          <a href="{{ url_for('login') }}" class="mr-4 underline">Login</a>
          <a href="{{ url_for('register') }}" class="underline">Register</a>
        {% endif %}
      </div>
    </div>
  </nav>

  <!-- Flash messages -->
  <div class="container mx-auto mt-4">
    {% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
        {% for category, message in messages %}
          <div class="mb-4 p-3 rounded text-white {% if category == 'success' %}bg-green-600{% elif category == 'danger' %}bg-red-600{% else %}bg-blue-600{% endif %}">
            {{ message }}
          </div>
        {% endfor %}
      {% endif %}
    {% endwith %}
  </div>

  <!-- Page content -->
  <div class="container mx-auto mt-6">
    {% block content %}
    {% endblock %}
  </div>
</body>
</html>

Highlights: Uses Tailwind CSS for layout and styling, Supports flash() messages with color-coded categories, Detects if the user is logged in and adjusts the navbar accordingly, Uses {% block title %} and {% block content %} for easy page extension.

templates/dashboard.html

A simple template that welcomes the user after they’ve logged in. It often shows personalized content like their name, email, or account status — and serves as the main access point to protected parts of the site. dashboard.html is the first page a user sees after logging in. It confirms they’ve successfully authenticated, shows personalized info (like their name or email), and can serve as a springboard for further actions like editing their profile, browsing data, or using protected features. The current_user is rovided by Flask-Login — gives access to user info.

{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
  <div class="bg-white p-6 rounded shadow-md max-w-md mx-auto">
    <h1 class="text-2xl font-bold mb-4">Welcome, {{ current_user.username }}!</h1>
    <p class="text-gray-700">You are now logged in.</p>
    <p class="mt-4 text-sm text-gray-500">Email: {{ current_user.email }}</p>
  </div>
{% endblock %}

Protected by @login_required. This route is only accessible to authenticated users. You can see the code @login_required.

@app.route('/dashboard')
@login_required
def dashboard():
    return render_template('dashboard.html')

config.py

Stores your project’s configuration. The SECRET_KEY, SQLALCHEMY_DATABASE_URI, and the MAIL_SERVER, MAIL_PORT, etc.You might also include separate Config, DevelopmentConfig, ProductionConfig classes. You might also include separate Config, DevelopmentConfig, oductionConfig classes. The SECRET_KEY is essential for securely signing session cookies and CSRF tokens — don’t expose it publicly.

requirements.txt

All Python packages needed to run the project, such as:

  • Flask
  • Flask-Login
  • Flask-WTF
  • Flask-Mail
  • Flask-Bootstrap
  • itsdangerous
  • email-validator

How it all Connects — A Step-by-Step Journey

When a new (unregistered) visitor arrives at the website, they are presented with a simple home page. In the top navigation bar, they’ll see a Register link — but not a Dashboard or Logout link, because they aren’t logged in yet.

  1. User clicks “Register”
    The browser opens a page displaying a form generated by register.html. This form comes from RegisterForm in forms.py, which is handled securely with Flask-WTF.
  2. User fills out the form and submits it
    The form sends a POST request to the /register route in routes.py. This route:

    • Validates the form data
    • Checks if the email is already taken
    • Creates a new User instance (from models.py)
    • Stores the hashed password using Werkzeug
  3. A confirmation email is sent
    A secure token is generated using tokens.py. The email.py file renders confirm_email.html and sends the email using Flask-Mail. The email contains a secure link like:
    http://yourdomain.com/confirm/eyJhbGciOiJIUzI1Ni...
  4. User clicks the confirmation link in their email
    The browser hits the /confirm/<token> route. This route:

    • Validates the token with tokens.py
    • Marks the user as confirmed in the database
    • Displays a success message using flash()
  5. User can now log in
    They go to the /login page and submit their credentials. Flask processes the LoginForm and:

Leave a Reply