Skip to content

Protecting Routes

You have set up Flask-Login, stored a user, and built a login form. Now for the part that makes it all worthwhile: locking a page so only logged-in users can see it.

After everything you have just set up, doing this takes exactly one line.


@login_required

Add @login_required above any route you want to protect:

from flask_login import login_required

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

That is it.

If someone who is not logged in tries to visit /dashboard, Flask-Login steps in before the function even runs. It sends them straight to the login page instead. The dashboard function never runs for them.

If they are logged in, they get through and see the page normally.


Telling Flask-Login where your login page is

For @login_required to know where to send people, you need to tell it which route is your login page. You already did this in the setup:

login_manager.login_view = "login"

"login" is the name of the function - the same name you gave your login route. Flask-Login uses it to build the redirect URL automatically.


Logging out

Logging out means telling Flask-Login to stop remembering this person. One function call does it:

from flask_login import logout_user, login_required

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('home'))

logout_user() clears the session. The user is now logged out. The redirect sends them back to the home page.

@login_required is on the logout route too. It makes sense - only someone who is already logged in should be able to log out. If a logged-out user somehow visits /logout directly, Flask-Login sends them to the login page instead.


Updating the navigation

Now that login state exists, the nav in base.html can change depending on whether someone is logged in.

You already know {% if %} from Jinja templates. You used it to show "Male" or "Female" depending on a value. Here it is the same idea - checking current_user.is_authenticated:

<nav>
  <a href="/">Home</a> |
  <a href="/about">About</a> |
  <a href="/contact">Contact</a> |
  {% if current_user.is_authenticated %}
    {{ current_user.username }} |
    <a href="/logout">Logout</a>
  {% else %}
    <a href="/login">Login</a>
  {% endif %}
</nav>

When logged in: shows the username and a Logout link. When logged out: shows a Login link.

Plain anchor tags - no new Bootstrap needed.


The complete app.py

Here is the full picture - every route, showing which are public and which are protected:

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

app = Flask(__name__)
app.secret_key = "change-this-to-something-random"

login_manager = LoginManager(app)
login_manager.login_view = "login"

@login_manager.user_loader
def load_user(user_id):
    return User.objects.get(id=user_id)

@app.route('/')
def home():
    return render_template('home.html')       # public - anyone can visit

@app.route('/about')
def about():
    return render_template('about.html')      # public - anyone can visit

@app.route('/dashboard')
@login_required                               # protected - must be logged in
def dashboard():
    return render_template('dashboard.html')

@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'))

@app.route('/logout')
@login_required                               # protected - only logged-in users can log out
def logout():
    logout_user()
    return redirect(url_for('home'))

if __name__ == '__main__':
    app.run(debug=True)

Two public routes, one protected route, one login route, one logout route. That is a complete working authentication system.


Challenge

Add the dashboard route and dashboard.html. Try visiting /dashboard without logging in - you should be sent to the login page. Log in, then try again - you should see the dashboard. Click Logout and try once more - back to the login page.


← The Login Form    Next: Exercises →