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.