Using Flask-Login to Handle User Accounts
Tags: flask, softwaredevelopment, python
Published: 2019-06-20 21:50:27

---
title: Using Flask-Login to Handle User Accounts
published: true
tags: Flask,Software Development,Python
canonical_url: https://hackersandslackers.com/authenticating-users-with-flask-login/
---

 ![Using Flask-Login to Handle User Accounts](https://hackersandslackers.com/content/images/2019/04/flasklogin.jpg)

We’ve covered a lot of Flask goodness in this series thus far. We fully understand how to structure a sensible application; we can serve up [**complex page templates**](https://hackersandslackers.com/powerful-page-templates-in-flask-with-jinja/), and have dived into [**interacting with databases using Flask-SQLAlchemy**](https://dev.to/hackersandslackers/manage-database-models-with-flask-sqlalchemy-22n8). For our next challenge, we’re going to need _all_ of the knowledge we've acquired thus far and much, much more. Welcome to the Super Bowl of Flask development. This. Is. Flask-Login.

[**Flask-Login**](https://flask-login.readthedocs.io/en/latest/) is a dope library which handles all aspects of user management, including vital nuances you might not expect. Some noteworthy features include securing parts of our app behind login walls, encrypting passwords, and handling sessions. Moreover, It plays nicely with other Flask libraries we’re already familiar with: [**Flask-SQLAlchemy**](https://dev.to/hackersandslackers/manage-database-models-with-flask-sqlalchemy-22n8) to create and fetch accounts, and [**Flask-WTForms**](https://hackersandslackers.com/guide-to-building-forms-in-flask/) for handling intelligent sign-up & log-in forms. This tutorial assumes you have _some_ working knowledge of these things.

Flask-Login is shockingly quite easy to use after the initial learning curve... but therein lies the catch. Perhaps I’m not the only one to have noticed, but most Flask-related documentation tends to be, well, God-awful. The community is riddled with helplessly outdated information; if you ever come across `flask.ext` in a tutorial, it is inherently worthless to anybody developing in 2019. To make matters worse, official Flask-Login documentation contains some artifacts which are just plain wrong. The documentation contradicts itself (I’ll show you what I mean), and offers little to no code examples to speak of. My only hope is that I might save somebody the endless headaches I’ve experienced myself.

## **Structuring Our Application**

Let’s start with installing dependencies. This should give you an idea of what you’re in for:

<!--kg-card-begin: markdown-->
```
$ pip3 install flask flask-login flask-sqlalchemy psycopg2-binary python-dotenv

```
<!--kg-card-end: markdown-->

Sweet. Let’s take this one step at a time, starting with our project file structure:

<!--kg-card-begin: markdown-->
```
flasklogin-tutorial
├── /login_tutorial
│ ├── __init__.py
│ ├── auth.py
│ ├── forms.py
│ ├── models.py
│ ├── routes.py
│ ├── /static
│ │ ├── /dist
│ │ │ ├── /css
│ │ │ │ ├── account.css
│ │ │ │ └── dashboard.css
│ │ │ └── /js
│ │ │ └── main.min.js
│ │ └── /src
│ │ ├── /js
│ │ │ └── main.js
│ │ └── /less
│ │ ├── account.less
│ │ ├── dashboard.less
│ │ └── vars.less
│ └── /templates
│ ├── dashboard.html
│ ├── layout.html
│ ├── login.html
│ ├── meta.html
│ ├── scripts.html
│ └── signup.html
├── config.py
├── requirements.txt
├── setup.py
├── start.sh
└── wsgi.py

```
<!--kg-card-end: markdown-->

Of course, I wouldn't be a gentleman unless I revealed my **config.py** as well:

<!--kg-card-begin: markdown-->
```
from os import environ

class Config:
    """Set Flask configuration vars from .env file."""

    # General Config
    SECRET_KEY = environ.get('SECRET_KEY')
    FLASK_APP = environ.get('FLASK_APP')
    FLASK_ENV = environ.get('FLASK_ENV')
    FLASK_DEBUG = environ.get('FLASK_DEBUG')

    # Database
    SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
    SQLALCHEMY_TRACK_MODIFICATIONS = os.environ.get('SQLALCHEMY_TRACK_MODIFICATIONS')

```
<!--kg-card-end: markdown-->

The configuration values live in a `.env` file, a practice I highly encourage. Of these configuration variables, **SECRET\_KEY** is where we should turn our attention. SECRET\_KEY is the equivalent of a password used to secure our app; it should be as long, nonsensical, and impossible-to-remember as humanly possible. Seriously: having your secret key compromised is the equivalent of feeding gremlins after midnight.

## Initializing Flask-Login

With a standard "application factory" app, setting up Flask-Login is no different from other Flask plugins (or whatever they're called now). This makes setting up easy; all we need to do is make sure **Flask-Login** is initialized in ` __init__.py` along with the rest of our plugins:

<!--kg-card-begin: markdown-->
```
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager

db = SQLAlchemy()
login_manager = LoginManager()

def create_app():
    """Construct the core application."""
    app = Flask( __name__ , instance_relative_config=False)

    # Application Configuration
    app.config.from_object('config.Config')

    # Initialize Plugins
    db.init_app(app)
    login_manager.init_app(app)

    with app.app_context():
        # Import parts of our application
        from . import routes
        from . import login
        app.register_blueprint(routes.main_bp)
        app.register_blueprint(login.login_bp)

        # Initialize Global db
        db.create_all()

        return app

```
<!--kg-card-end: markdown-->

In the above example, we're using the minimal number of plug-ins to get logins working: **Flask-SQLAlchemy** and **Flask-Login**.

To keep our sanity, we're going to separate our login routes from our main application routes and logic. This is why we register a Blueprint called **auth\_bp** , imported from a file called `auth.py`. Our “main” application (AKA anything that isn’t logging in) will instead live in `routes.py`, in a Blueprint called **main\_bp**. We'll come back to these in a moment

## Creating a User Model

We'll save our **User** model in `models.py`. There are a few things to keep in mind when creating models compatible with Flask-Login- the most important being the utilization of `UserMixin` from the `flask_login` library. When we inherit our class from **UserMixin,** our model is immediately extended to include all the methods necessary for Flask-Login to work. This is by far the easiest way of creating a User model. I won't bother getting into details of what these classes do, because if you simply begin your class with `class User(UserMixin, db.Model):`, you genuinely don't need to understand any of it:

<!--kg-card-begin: markdown-->
```
from . import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin, db.Model):
    """Model for user accounts."""

    __tablename__ = 'flasklogin-users'

    id = db.Column(db.Integer,
                   primary_key=True)
    name = db.Column(db.String,
                     nullable=False,
                     unique=False)
    email = db.Column(db.String(40),
                      unique=True,
                      nullable=False)
    password = db.Column(db.String(200),
                         primary_key=False,
                         unique=False,
                         nullable=False)
    website = db.Column(db.String(60),
                        index=False,
                        unique=False,
                        nullable=True)
    created_on = db.Column(db.DateTime,
                           index=False,
                           unique=False,
                           nullable=True)
    last_login = db.Column(db.DateTime,
                           index=False,
                           unique=False,
                           nullable=True)

    def set_password(self, password):
        """Create hashed password."""
        self.password = generate_password_hash(password, method='sha256')

    def check_password(self, password):
        """Check hashed password."""
        return check_password_hash(self.password, password)

    def __repr__ (self):
        return '<User {}>'.format(self.username)

```
<!--kg-card-end: markdown-->

The `set_password` and `check_password` methods don't necessarily need to live inside our User model, but it's nice to keep related logic bundled together and out of our routes.

You may notice that our password field explicitly allows 200 characters: this is because our database will be storing hashed passwords. Thus, even if a user's password is 8 characters long, the string in our database will look much different.

## Creating Log-in and Sign-up Forms

If you're well versed in **WTForms** , our form logic in **forms.py** probably looks as you'd expect. Of course, the constraints we set here are to handle front-end validation only:

<!--kg-card-begin: markdown-->
```
from wtforms import Form, StringField, PasswordField, validators, SubmitField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, Length, Optional

class SignupForm(Form):
    """User Signup Form."""

    name = StringField('Name',
                        validators=[DataRequired(message=('Enter a fake name or something.'))])
    email = StringField('Email',
                        validators=[Length(min=6, message=('Please enter a valid email address.')),
                                    Email(message=('Please enter a valid email address.')),
                                    DataRequired(message=('Please enter a valid email address.'))])
    password = PasswordField('Password',
                             validators=[DataRequired(message='Please enter a password.'),
                                         Length(min=6, message=('Please select a stronger password.')),
                                         EqualTo('confirm', message='Passwords must match')])
    confirm = PasswordField('Confirm Your Password',)
    website = StringField('Website',
                          validators=[Optional()])
    submit = SubmitField('Register')

class LoginForm(Form):
    """User Login Form."""

    email = StringField('Email', validators=[DataRequired('Please enter a valid email address.'),
                                             Email('Please enter a valid email address.')])
    password = PasswordField('Password', validators=[DataRequired('Uhh, your password tho?')])
    submit = SubmitField('Log In')

```
<!--kg-card-end: markdown-->

With those out of the way, let's look at how we implement these on the Jinja side.

### signup.html
<!--kg-card-begin: markdown-->
```
{% extends "layout.html" %}

{% block pagestyles %}
    <link href="{{ url_for('static', filename='dist/css/account.css') }}" rel="stylesheet" type="text/css">
{% endblock %}

{% block content %}
  <div class="formwrapper">
    <form method=post>
      <div class="logo">
        <img src="{{ url_for('static', filename='dist/img/logo.png') }}">
      </div>
      {% for message in get_flashed_messages() %}
        <div class="alert alert-warning">
            <button type="button" class="close" data-dismiss="alert">&times;</button>
            {{ message }}
        </div>
      {% endfor %}
      <h1>Sign Up</h1>
      <div class="name">
        {{ form.name.label }}
        {{ form.name(placeholder='John Smith') }}
        {% if form.name.errors %}
          <ul class="errors">
            {% for error in form.email.errors %}<li>{{ error }}</li>{% endfor %}
          </ul>
        {% endif %}
      </div>
      <div class="email">
        {{ form.email.label }}
        {{ form.email(placeholder='youremail@example.com') }}
        {% if form.email.errors %}
          <ul class="errors">
            {% for error in form.email.errors %}<li>{{ error }}</li>{% endfor %}
          </ul>
        {% endif %}
      </div>
      <div class="password">
        {{ form.password.label }}
        {{ form.password }}
        {% if form.password.errors %}
          <ul class="errors">
            {% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}
          </ul>
        {% endif %}
      </div>
      <div class="confirm">
        {{ form.confirm.label }}
        {{ form.confirm }}
        {% if form.confirm.errors %}
          <ul class="errors">
            {% for error in form.confirm.errors %}<li>{{ error }}</li>{% endfor %}
          </ul>
        {% endif %}
      </div>
      <div class="website">
        {{ form.website.label }}
        {{ form.website(placeholder='http://example.com') }}
      </div>
      <div class="submitbutton">
        <input id="submit" type="submit" value="Submit">
      </div>
    </form>
    <div class="loginsignup">
      <span>Already have an account? <a href="{{ url_for('auth_bp.login_page') }}">Log in.</a><span>
    </div>
  </div>
{% endblock %}

```
<!--kg-card-end: markdown-->
### login.py
<!--kg-card-begin: markdown-->
```
{% extends "layout.html" %}

{% block pagestyles %}
  <link href="{{ url_for('static', filename='dist/css/account.css') }}" rel="stylesheet" type="text/css">
{% endblock %}

{% block content %}
  <div class="formwrapper">
    <form method=post>
      <div class="logo">
        <img src="{{ url_for('static', filename='dist/img/logo.png') }}">
      </div>
      {% for message in get_flashed_messages() %}
        <div class="alert alert-warning">
            <button type="button" class="close" data-dismiss="alert">&times;</button>
            {{ message }}
        </div>
      {% endfor %}
      <h1>Log In</h1>
      <div class="email">
         {{ form.email.label }}
         {{ form.email(placeholder='youremail@example.com') }}
         {% if form.email.errors %}
           <ul class="errors">
             {% for error in form.email.errors %}<li>{{ error }}</li>{% endfor %}
           </ul>
         {% endif %}
      </div>
      <div class="password">
        {{ form.password.label }}
        {{ form.password }}
        {% if form.email.errors %}
          <ul class="errors">
            {% for error in form.password.errors %}<li>{{ error }}</li>{% endfor %}
          </ul>
        {% endif %}
      </div>
      <div class="submitbutton">
        <input id="submit" type="submit" value="Submit">
      </div>
      <div class="loginsignup">
        <span>Don't have an account? <a href="{{ url_for('auth_bp.signup_page') }}">Sign up.</a><span>
        </div>
    </form>
  </div>
{% endblock %}

```
<!--kg-card-end: markdown-->

Excellent: the stage is set to start kicking some ass.

## Creating Our Login Routes

Let us turn our attention to the heart of the logic we'll be writing in **auth.py** :

<!--kg-card-begin: markdown-->
```
import os
from flask import redirect, render_template, flash, Blueprint, request, session, url_for
from flask_login import login_required, logout_user, current_user, login_user
from flask import current_app as app
from werkzeug.security import generate_password_hash, check_password_hash
from .forms import LoginForm, SignupForm
from .models import db, User
from . import login_manager

# Blueprint Configuration
auth_bp = Blueprint('auth_bp', __name__ ,
                    template_folder='templates',
                    static_folder='static')
assets = Environment(app)

@auth_bp.route('/login', methods=['GET', 'POST'])
def login_page():
    """User login page."""
    # Bypass Login screen if user is logged in
    if current_user.is_authenticated:
        return redirect(url_for('main_bp.dashboard'))
    login_form = LoginForm(request.form)
    # POST: Create user and redirect them to the app
    if request.method == 'POST':
        ...
    # GET: Serve Log-in page
    return render_template('login.html',
                           form=LoginForm(),
                           title='Log in | Flask-Login Tutorial.',
                           template='login-page',
                           body="Log in with your User account.")

@auth_bp.route('/signup', methods=['GET', 'POST'])
def signup_page():
    """User sign-up page."""
    signup_form = SignupForm(request.form)
    # POST: Sign user in
    if request.method == 'POST':
        ...
    # GET: Serve Sign-up page
    return render_template('/signup.html',
                           title='Create an Account | Flask-Login Tutorial.',
                           form=SignupForm(),
                           template='signup-page',
                           body="Sign up for a user account.")

```
<!--kg-card-end: markdown-->

Here we find two separate skeleton routes for **Sign up** and **Log in**. Without the authentication logic added quite yet, these routes look almost identical thus far.

Each time a user visits a page in your app, the corresponding route is sent a `request` object. This object contains contextual information about the request made by the user, such as the type of request (GET or POST), any form data which was submitted, etc. We leverage this to see whether the user is just arriving at the page for the first time (a GET request), or if they're attempting to sign in (a POST request). The fairly clever takeaway here is that our login pages verify users by making POST requests to themselves: this allows us to keep all logic related to logging in or signing up in a single route.

### Signing Up

We're able to validate the submitted form by importing the `SignupForm` class and passing `request.form` as the form in question. `if signup_form.validate()` checks the information submitted by the user against all the form's validators. If any of the validators are not met, the user is redirected back to the signup form with error messages present.

Assuming that our user isn't inept, we can move on with our logic. First, we need to make sure a user with the provided email doesn't already exist:

<!--kg-card-begin: markdown-->
```
...

@auth_bp.route('/signup', methods=['GET', 'POST'])
def signup_page():
    """User sign-up page."""
    signup_form = SignupForm(request.form)
    # POST: Sign user in
    if request.method == 'POST':
        if signup_form.validate():
            # Get Form Fields
            name = request.form.get('name')
            email = request.form.get('email')
            password = request.form.get('password')
            website = request.form.get('website')
            existing_user = User.query.filter_by(email=email).first()
            if existing_user is None:
                user = User(name=name,
                            email=email,
                            password=generate_password_hash(password, method='sha256'),
                            website=website)
                db.session.add(user)
                db.session.commit()
                login_user(user)
                return redirect(url_for('main_bp.dashboard'))
            flash('A user already exists with that email address.')
            return redirect(url_for('auth_bp.signup_page'))
    # GET: Serve Sign-up page
    return render_template('/signup.html',
                           title='Create an Account | Flask-Login Tutorial.',
                           form=SignupForm(),
                           template='signup-page',
                           body="Sign up for a user account.")

```
<!--kg-card-end: markdown-->

If `existing_user is None`, we're all clear to actually clear to create a new user record. We create an instance of our User model via `user = User(...)`. We then add the user via standard SQLAlchemy syntax and finally use the imported method `login_user()` to log the user in.

If everything goes well, the user will finally be redirected to the main application, which is handled by `return redirect(url_for('main_bp.dashboard'))`:

<!--kg-card-begin: image-->

![Using Flask-Login to Handle User Accounts](https://hackersandslackers.com/content/images/2019/04/signup.gif)<figcaption>A successful user log in.</figcaption>

<!--kg-card-end: image-->

And here's what will happen if we log out and try to sign up with the same information:

<!--kg-card-begin: image-->

![Using Flask-Login to Handle User Accounts](https://hackersandslackers.com/content/images/2019/04/signupfailed.gif)<figcaption>Attempting to sign up with an existing email</figcaption>

<!--kg-card-end: image-->
### Logging In

Moving on to our login route:

<!--kg-card-begin: markdown-->
```
@auth_bp.route('/login', methods=['GET', 'POST'])
def login_page():
    """User login page."""
    # Bypass Login screen if user is logged in
    if current_user.is_authenticated:
        return redirect(url_for('main_bp.dashboard'))
    login_form = LoginForm(request.form)
    # POST: Create user and redirect them to the app
    if request.method == 'POST':
        if login_form.validate():
            # Get Form Fields
            email = request.form.get('email')
            password = request.form.get('password')
            # Validate Login Attempt
            user = User.query.filter_by(email=email).first()
            if user:
                if user.check_password(password=password):
                    login_user(user)
                    next = request.args.get('next')
                    return redirect(next or url_for('main_bp.dashboard'))
        flash('Invalid username/password combination')
        return redirect(url_for('auth_bp.login_page'))
    # GET: Serve Log-in page
    return render_template('login.html',
                           form=LoginForm(),
                           title='Log in | Flask-Login Tutorial.',
                           template='login-page',
                           body="Log in with your User account.")

```
<!--kg-card-end: markdown-->

This should mostly look the same! our logic is identical up until the point where we check to see if the user exists. This time, a match results in success as opposed to a failure. Continuing, we then use `user.check_password()` to check the hashed password we created earlier with `user.generate_password_hash()`. Both of these methods handle the encrypting and decrypting of passwords on their own (based on that SECRET\_KEY we created earlier) to ensure that nobody (not even us) has any business looking at user passwords.

As with last time, a successful login ends in `login_user(user)`. Our redirect logic is little more sophisticated this time around: instead of always sending the user back to the dashboard, we check for `next`, which is a parameter stored in the query string of the current user. If the user attempted to access our app before logging in, `next` would equal the page they had attempted to reach: this allows us wall-off our app from unauthorized users, and then drop users off at the page they attempted to reach before they logged in:

<!--kg-card-begin: image-->

![Using Flask-Login to Handle User Accounts](https://hackersandslackers.com/content/images/2019/04/login.gif)<figcaption>A successful log in</figcaption>

<!--kg-card-end: image-->
### IMPORTANT: Login Helpers

Before your app can work like the above, we need to finish **auth.py** by providing a few more routes:

<!--kg-card-begin: markdown-->
```
@auth_bp.route("/logout")
@login_required
def logout_page():
    """User log-out logic."""
    logout_user()
    return redirect(url_for('auth_bp.login_page'))

@login_manager.user_loader
def load_user(user_id):
    """Check if user is logged-in on every page load."""
    if user_id is not None:
        return User.query.get(user_id)
    return None

@login_manager.unauthorized_handler
def unauthorized():
    """Redirect unauthorized users to Login page."""
    flash('You must be logged in to view that page.')
    return redirect(url_for('auth_bp.login_page'))

```
<!--kg-card-end: markdown-->

Our first route, `logout_page`, handles the logic of users logging out. This will simply end the user's session and redirect them to the login screen.

`load_user` is critical for making our app work: before every page load, our app must verify whether or not the user is logged in (or _still_ logged in after time has elapsed). `user_loader` loads users by their unique ID. If a user is returned, this signifies a logged-out user. Otherwise, when `None` is returned, the user is logged out.

Lastly, we have the `unauthorized` route, which uses the `unauthorized_handler` decorator for dealing with unauthorized users. Any time a user attempts to hit our app and is unauthorized, this route will fire.

## The Last Piece: routes.py

The last thing we'll cover is how to protect parts of our app from unauthorized users. Here's what we have in **routes.py** :

<!--kg-card-begin: markdown-->
```
import os
from flask import Blueprint, render_template
from flask_assets import Environment, Bundle
from flask_login import current_user
from flask import current_app as app
from .models import User
from flask_login import login_required

# Blueprint Configuration
main_bp = Blueprint('main_bp', __name__ ,
                    template_folder='templates',
                    static_folder='static')
assets = Environment(app)

@main_bp.route('/', methods=['GET'])
@login_required
def dashboard():
    """Serve logged in Dashboard."""
    return render_template('dashboard.html',
                           title='Flask-Login Tutorial.',
                           template='dashboard-template',
                           current_user=current_user,
                           body="You are now logged in!")

```
<!--kg-card-end: markdown-->

The magic here is all contained within the `@login_required` decorator. When this decorator is present on a route, the following things happen:

- The `@login_manager.user_loader` route we created determines whether or not the user is authorized to view the page (logged in). If the user is logged in, they'll be permitted to view the page.
- If the user is not logged in, the user will be redirected as per the logic in the route decorated with `@login_manager.unauthorized_handler`.
- The name of the route the user attempted to access will be stored in the URL as `?url=[name-of-route]`. This what allows `next` to work.

### There You Have It

If you've made it this far, I commend you for your courage. To reward your accomplishments, I've published the [source code for this tutorial on Github](https://github.com/toddbirchard/flasklogin-tutorial) for your reference. Godspeed, brave adventurer.