Skip to content

The Login Form

Everything is set up. Now we build the page where a user types their username and password.


Why a regular form POST - not fetch

Every time you sent data to Flask before, you used fetch. This time we are using a regular HTML form POST instead.

Here is why: after a successful login, Flask-Login needs to attach information to the user's session before the next page loads. A regular form POST causes a full page reload, which gives Flask time to do that properly. With fetch, the page does not reload, and Flask-Login does not get that chance.

You already know how regular form POST works. If you need a reminder, see Receiving Data in Flask.


login.html

{% extends "base.html" %}

{% block content %}
  <h1>Login</h1>

  {% if error %}
    <p style="color:red;">{{ error }}</p>
  {% endif %}

  <form method="POST" action="/login">
    <input name="username" type="text"     class="form-control mb-2" placeholder="Username">
    <input name="password" type="password" class="form-control mb-2" placeholder="Password">
    <button type="submit" class="btn btn-primary">Login</button>
  </form>
{% endblock %}

{% if error %} shows an error message if one was passed from Flask. You already know {% if %} from Jinja. If there is no error, this section is invisible.


The Flask routes

Add these to app.py:

from flask import Flask, render_template, request, redirect, url_for
from flask_login import LoginManager, login_user, current_user
from werkzeug.security import check_password_hash
from database import User

# ... (LoginManager setup from the previous page) ...

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')

    username = request.form["username"]
    password = request.form["password"]

    user = User.objects.filter(username=username).first()

    if user is None or not check_password_hash(user.password, password):
        return render_template('login.html', error="Wrong username or password.")

    login_user(user)
    return redirect(url_for('home'))

Walking through it step by step:

if request.method == 'GET' - if someone just visits /login in their browser, show them the empty form.

request.form["username"] - read what was typed in the username field. Same as you did in Receiving Data in Flask.

User.objects.filter(username=username).first() - look up the user by username. .first() returns the first match, or None if no match was found.

check_password_hash(user.password, password) - check the entered password against the stored scrambled one. Returns True if they match.

login_user(user) - this is the one call that tells Flask-Login "this person is now logged in". One line and Flask-Login handles everything else - it stores the user's ID and will remember them on every page they visit until they log out.

redirect(url_for('home')) - send them to the home page now that they are logged in.


current_user

Flask-Login gives you a variable called current_user that is available in every template automatically. It represents whoever is logged in right now.

In any template:

{% if current_user.is_authenticated %}
  <p>Hello, {{ current_user.username }}!</p>
{% else %}
  <p>You are not logged in.</p>
{% endif %}

is_authenticated is True when someone is logged in, False when they are not. You already know {% if %} so this should look familiar.


Challenge

Add the login route to app.py and create login.html. Run the app, visit /login, and log in with the alice account you created in seed.py. After login, you should be redirected to the home page. Try entering a wrong password - you should see the red error message.


← Storing Users with PookieDB    Next: Protecting Routes →