Skip to main content
A view function is the code you write to respond to requests to your application. Flask uses patterns to match the incoming request URL to the view that should handle it.

Create a Blueprint

A Blueprint is a way to organize a group of related views and other code. Rather than registering views directly with an application, they are registered with a blueprint. Then the blueprint is registered with the application when it is available in the factory function. Flaskr will have two blueprints:
  1. Authentication functions
  2. Blog posts functions

Authentication Blueprint

1

Create the auth blueprint

Create flaskr/auth.py:
flaskr/auth.py
import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash

from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')
This creates a Blueprint named 'auth'. The url_prefix will be prepended to all URLs associated with the blueprint.
2

Register the blueprint

Import and register the blueprint from the factory in flaskr/__init__.py:
flaskr/__init__.py
def create_app():
    app = ...
    # existing code omitted

    from . import auth
    app.register_blueprint(auth.bp)

    return app

The Register View

When the user visits /auth/register, the register view will return HTML with a form for them to fill out. When they submit the form, it will validate their input and either show the form again with an error message or create the new user and go to the login page.
flaskr/auth.py
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'

        if error is None:
            try:
                db.execute(
                    "INSERT INTO user (username, password) VALUES (?, ?)",
                    (username, generate_password_hash(password)),
                )
                db.commit()
            except db.IntegrityError:
                error = f"User {username} is already registered."
            else:
                return redirect(url_for("auth.login"))

        flash(error)

    return render_template('auth/register.html')

How It Works

  1. @bp.route('/register', methods=('GET', 'POST')) - Associates the URL /register with the register view function
  2. Validation - Checks that username and password are not empty
  3. Database Insert - Uses parameterized queries with ? placeholders to prevent SQL injection attacks
  4. Password Security - generate_password_hash() securely hashes the password before storing it
  5. Error Handling - If the username already exists, IntegrityError is caught and shown to the user
  6. Redirect - After successful registration, redirect() sends the user to the login page. url_for() generates the URL based on the view name
  7. Flash Messages - flash() stores messages that can be retrieved when rendering the template

The Login View

The login view follows the same pattern as the register view:
flaskr/auth.py
@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        db = get_db()
        error = None
        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone()

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'

        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')

Differences from Register

  1. The user is queried first and stored in a variable for later use
  2. fetchone() returns one row from the query. If the query returned no results, it returns None
  3. check_password_hash() hashes the submitted password and securely compares it to the stored hash
  4. session is a dict that stores data across requests. When validation succeeds, the user’s id is stored in a new session. The data is stored in a cookie that is sent to the browser

Load Logged In User

At the beginning of each request, if a user is logged in their information should be loaded and made available to other views:
flaskr/auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()
bp.before_app_request() registers a function that runs before the view function, no matter what URL is requested. It checks if a user id is stored in the session and gets that user’s data from the database, storing it on g.user.

The Logout View

To log out, remove the user id from the session:
flaskr/auth.py
@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))

Require Authentication in Other Views

Creating, editing, and deleting blog posts will require a user to be logged in. A decorator can be used to check this for each view:
flaskr/auth.py
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))

        return view(**kwargs)

    return wrapped_view
This decorator returns a new view function that wraps the original view it’s applied to. The new function checks if a user is loaded and redirects to the login page otherwise. You’ll use this decorator when writing the blog views.

Endpoints and URLs

The url_for() function generates the URL to a view based on a name and arguments. The name associated with a view is called the endpoint, and by default it’s the same as the name of the view function. For example, the hello() view added to the app factory earlier has the name 'hello' and can be linked to with url_for('hello'). When using a blueprint, the name of the blueprint is prepended to the name of the function, so the endpoint for the login function is 'auth.login'.

Complete Auth Module

Here’s the complete flaskr/auth.py file:
flaskr/auth.py
import functools

from flask import Blueprint
from flask import flash
from flask import g
from flask import redirect
from flask import render_template
from flask import request
from flask import session
from flask import url_for
from werkzeug.security import check_password_hash
from werkzeug.security import generate_password_hash

from .db import get_db

bp = Blueprint("auth", __name__, url_prefix="/auth")


def login_required(view):
    """View decorator that redirects anonymous users to the login page."""

    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for("auth.login"))

        return view(**kwargs)

    return wrapped_view


@bp.before_app_request
def load_logged_in_user():
    """If a user id is stored in the session, load the user object from
    the database into ``g.user``."""
    user_id = session.get("user_id")

    if user_id is None:
        g.user = None
    else:
        g.user = (
            get_db().execute("SELECT * FROM user WHERE id = ?", (user_id,)).fetchone()
        )


@bp.route("/register", methods=("GET", "POST"))
def register():
    """Register a new user.

    Validates that the username is not already taken. Hashes the
    password for security.
    """
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        db = get_db()
        error = None

        if not username:
            error = "Username is required."
        elif not password:
            error = "Password is required."

        if error is None:
            try:
                db.execute(
                    "INSERT INTO user (username, password) VALUES (?, ?)",
                    (username, generate_password_hash(password)),
                )
                db.commit()
            except db.IntegrityError:
                error = f"User {username} is already registered."
            else:
                return redirect(url_for("auth.login"))

        flash(error)

    return render_template("auth/register.html")


@bp.route("/login", methods=("GET", "POST"))
def login():
    """Log in a registered user by adding the user id to the session."""
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        db = get_db()
        error = None
        user = db.execute(
            "SELECT * FROM user WHERE username = ?", (username,)
        ).fetchone()

        if user is None:
            error = "Incorrect username."
        elif not check_password_hash(user["password"], password):
            error = "Incorrect password."

        if error is None:
            session.clear()
            session["user_id"] = user["id"]
            return redirect(url_for("index"))

        flash(error)

    return render_template("auth/login.html")


@bp.route("/logout")
def logout():
    """Clear the current session, including the stored user id."""
    session.clear()
    return redirect(url_for("index"))

Build docs developers (and LLMs) love