Handle User Accounts & Authentication in Flask with Flask-Login
Tags: flask, software, python
Published: 2020-03-02 12:40:34

---
title: Handle User Accounts & Authentication in Flask with Flask-Login
published: true
date: 2020-03-02 12:40:00 UTC
tags: Flask,Software,Python
series: Build Flask Python Apps
canonical_url: https://hackersandslackers.com/flask-login-user-authentication/
---

 ![Handle User Accounts & Authentication in Flask with Flask-Login](https://storage.googleapis.com/hackersandslackers-cdn/2019/06/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 with Jinja, and we've tackled [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 that handles all aspects of user management, including user sign-ups, encrypting passwords, handling sessions, and securing parts of our app behind login walls. Flask-Login also happens to play nicely with other Flask libraries we’re already familiar with! There's built-in support for storing user data in a database via **Flask-SQLAlchemy** , while **Flask-WTForms** covers the subtleties of effective sign-up & log-in forms. This tutorial assumes you have _some_ working knowledge of these libraries.

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 `import flask.ext.plugin_name` 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.

## Getting Started

Let’s start with installing dependencies. Depending on which flavor of database you prefer, install either `psycopg2-binary` or `pymysql` along with the following:

```
$ pip3 install flask flask-login flask-sqlalchemy flask-wtf
```

It should be clear as to why we need each of these packages. Handling user accounts requires a database to store user data, hence `flask-sqlalchemy`. We'll be capturing that data via forms using `flask-wtf`.

I'm going to store all routes related to user authentication under **auth.py**, and the rest of our "logged-in" routes in **routes.py**. The rest should be clear as per standard procedure:

```shell
flasklogin-tutorial
├── /flask_login_tutorial
│   ├── /static
│   ├── /templates
│   ├── __init__.py
│   ├── auth.py
│   ├── forms.py
│   ├── models.py
│   └── routes.py
├── config.py
└── wsgi.py
```

### Configuring For Flask-Login

I wouldn't be a gentleman unless I revealed my **config.py** file. Again, this should all be straightforward if you've been following the rest of our series:

```python
import os


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

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

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

Of these variables, **SECRET\_KEY** and **SQLALCHEMY\_DATABASE\_URI** deserve our attention. **SQLALCHEMY\_DATABASE\_URI** is a given, as this is how we'll be connecting to our database.

Flask's **SECRET\_KEY** variable is a string from which all of our user's passwords (or other sensitive information) will be encrypted. We should strive to set this string to be as long, nonsensical, and impossible-to-remember as humanly possible. **Protect this string with your life**: anybody who gets their hands on your app's secret key has the power to potentially _unencrypted_ all user passwords in your application. In an ideal world, even _you_ shouldn't know your own key. Seriously: having your secret key compromised is the equivalent of feeding gremlins after midnight.

## Initializing Flask-Login

Setting up Flask-Login via the [application factory pattern](https://hackersandslackers.com/flask-application-factory) is no different from using any other Flask plugin (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, as we do with **Flask-SQLAlchemy** :

```python
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
```
<figcaption> __init__.py</figcaption>

This is the minimum we need to set up Flask-Login properly.

As mentioned earlier, we're separating routes pertaining to user authentication from our main application routes. We handle this by registering two blueprints: **auth\_bp** is imported from **auth.py** , and our “main” application routes are associated with **main\_bp** from **routes.py**. We'll be digging into both of these blueprints, but let's first turn our focus to preparing our database to store users.

## Creating a User Model

Flask-Login is tightly coupled with Flask-SQLAlchemy's ORM, which makes creating and validating users trivially easy. All we need to do upfront is create a **User** model, which we'll keep in **models.py**. It helps to be familiar with the [basics creating database models in Flask](https://hackersandslackers.com/flask-sqlalchemy-database-models/) here; if you've skipped ahead, well... that's your problem.

The most notable tidbit for creating a User model is a nifty shortcut from Flask-Login called the **UserMixin**. **UserMixin** is a helper provided by the Flask-Login library to provide boilerplate methods necessary for managing users. Models which inherit UserMixin immediately gain access to 4 useful methods:

- `is_authenticated`: Checks to see if the current user is already authenticated, thus allowing them to bypass login screens.
- `is_active`: In the event that your app supports disabling or temporarily banning accounts, we can check `if user.is_active()` to handle a case where their account exists, but have been banished from the land.
- `is_anonymous`: Many apps have a case where user accounts aren't entirely black-and-white, and anonymous users have access to interact without authenticating. This method might come in handy for allowing anonymous blog comments (which is madness, by the way).
- `get_id`: Fetches a unique ID identifying the user.

Creating a User model via **UserMixin** is by far the easiest way of getting started- the bulk of what remains is specifying the fields we want to capture for users. At a minimum, I'd suggest a username/email and password:

```python
"""Database models."""
from . import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash


class User(UserMixin, db.Model):
    """User account model."""

    __tablename__ = 'flasklogin-users'
    id = db.Column(
        db.Integer,
        primary_key=True
    )
    name = db.Column(
        db.String(100),
        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)
```
<figcaption>models.py</figcaption>

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.

It's nice to keep logic related to users bundled in our model and out of our routes, hence the `set_password()` and `check_password()` methods. Both of these methods use the `werkzeug` library to handle the hashing and checking of passwords (this is what depends on our **SECRET\_KEY** from earlier to store passwords securely).

## Creating Log-in and Sign-up Forms

Hopefully you've become somewhat acquainted with[Flask-WTF or WTForms](https://hackersandslackers.com/flask-wtforms-forms) in the past. We create two form classes in **forms.py** which cover our most essential needs: a sign-up form, and a log-in form:

```python
"""Sign-up & log-in forms."""
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import (
    DataRequired,
    Email,
    EqualTo,
    Length,
    Optional
)


class SignupForm(FlaskForm):
    """User Sign-up Form."""
    name = StringField(
        'Name',
        validators=[DataRequired()]
    )
    email = StringField(
        'Email',
        validators=[
            Length(min=6),
            Email(message='Enter a valid email.'),
            DataRequired()
        ]
    )
    password = PasswordField(
        'Password',
        validators=[
            DataRequired(),
            Length(min=6, message='Select a stronger password.')
        ]
    )
    confirm = PasswordField(
        'Confirm Your Password',
        validators=[
            DataRequired(),
            EqualTo('password', message='Passwords must match.')
        ]
    )
    website = StringField(
        'Website',
        validators=[Optional()]
    )
    submit = SubmitField('Register')


class LoginForm(FlaskForm):
    """User Log-in Form."""
    email = StringField(
        'Email',
        validators=[
            DataRequired(),
            Email(message='Enter a valid email.')
        ]
    )
    password = PasswordField('Password', validators=[DataRequired()])
    submit = SubmitField('Log In')
```
<figcaption>forms.py</figcaption>

Be sure to add fields in your form for whichever fields you specified in your **User** model, unless you're okay with having users change this information later. In my case, `created_on` and `last_login` are empty as these are easily handled on the SQL side of things.

This is normally where we'd expect to jump into creating routes for our signup and login pages. Instead of blowing your mind with the information overload that section is about to inflict on your unsuspecting mind, let's get comfortable by quickly looking at the Jinja templates for the signup and login forms we just created.

### signup.jinja2

The signup form is the heavier of the two forms as there's far more validation associated with creating a brand new user account as opposed to validating an email and password. The form template below wraps each field of the signup form in a `<fieldset>` tag, where we keep our field, our field's label, and all error message handling associated with submitting an invalid form:

```html
{% extends "layout.jinja2" %}


{% block content %}
  <div class="form-wrapper">

    <div class="logo">
      <img
        src="{{ url_for('static', filename='dist/img/logo.png') }}"
        alt="logo"
      />
    </div>

    {% for message in get_flashed_messages() %}
      <div class="alert alert-warning">
        <button
          type="button"
          class="close"
          data-dismiss="alert">
          x
        </button>
        {{ message }}
      </div>
    {% endfor %}

    <h1>Sign Up</h1>

    <form method="POST" action="/signup">
      {{ form.csrf_token }}

      <fieldset 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 %}
      </fieldset>

      <fieldset 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 %}
      </fieldset>

      <fieldset 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 %}
      </fieldset>

      <fieldset 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 %}
      </fieldset>

      <fieldset class="website">
        {{ form.website.label }}
        {{ form.website(placeholder='https://example.com') }}
      </fieldset>

      <div class="submit-button">
        {{ form.submit }}
      </div>

    </form>

    <div class="login-signup">
      <div>
        <span>Already have an account?</span>
        <a href="{{ url_for('auth_bp.login') }}">Log in.</a>
      </div>
    </div>
  </div>

{% endblock %}
```
<figcaption>signup.jinja</figcaption>
### login.jinja

The login form template contains essentially the same structure as our signup form, but with fewer fields:

```html
{% extends "layout.jinja2" %}


{% block content %}
  <div class="form-wrapper">

    <div class="logo">
      <img
        src="{{ url_for('static', filename='dist/img/logo.png') }}"
        alt="logo"
      />
    </div>

    {% for message in get_flashed_messages() %}
      <div class="alert alert-warning">
        <button
          type="button"
          class="close"
          data-dismiss="alert">
            x
          </button>
        {{ message }}
      </div>
    {% endfor %}

    <h1>Log In</h1>

    <form method="POST" action="/login">
      {{ form.csrf_token }}

      <fieldset 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 %}
      </fieldset>

      <fieldset 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 %}
      </fieldset>

      <div class="submit-button">
        {{ form.submit }}
      </div>

      <div class="login-signup">
        <span>Don't have an account?</span>
        <a href="{{ url_for('auth_bp.signup') }}">Sign up.</a>
      </div>

    </form>
  </div>

{% endblock %}
```
<figcaption>login.jinja2</figcaption>

The stage is set to start kicking some ass. Let's dig in.

## Creating Our Login Routes

Let's get things started by setting up a Blueprint for our authentication-related routes:

```python
"""Routes for user authentication."""
from flask import Blueprint


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


@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    # Login route logic goes here


@auth_bp.route('/signup', methods=['GET', 'POST'])
def signup():
    # Signup logic goes here
    ...
```
<figcaption>auth.py</figcaption>

Now we can start fleshing our signup and login routes. Before we can log anybody in, we need to make sure they can sign up.

### Signing Up

The skeleton of our signup route needs to handle GET requests when user land on the page for the first time, and POST requests when users attempt to submit the signup form:

```python
...
from flask import Blueprint, render_template, request
from .forms import SignupForm

...

@auth_bp.route('/signup', methods=['GET', 'POST'])
def signup():
    """
    User sign-up page.

    GET requests serve sign-up page.
    POST requests validate form & user creation.
    """
    form = SignupForm()
    if form.validate_on_submit():
        # User sign-up logic will go here.
        ...
    return render_template(
        'signup.jinja2',
        title='Create an Account.',
        form=form,
        template='signup-page',
        body="Sign up for a user account."
    )
```
<figcaption>auth.py</figcaption>

Our route will check all cases of a user attempting to sign in first by checking the HTTP method via Flask's **request** object. If the user is arriving for the first time, our route falls back to serve the **signup.jinja2** template via  `render_template()`.

Time to introduce our first taste of **flask\_login** logic:

```python
from flask import Blueprint, redirect, render_template, flash, request, session, url_for
from flask_login import login_required, logout_user, current_user, login_user
from .forms import LoginForm, SignupForm
from .models import db, User
from . import login_manager

...

@auth_bp.route('/signup', methods=['GET', 'POST'])
def signup():
    """
    User sign-up page.

    GET requests serve sign-up page.
    POST requests validate form & user creation.
    """
    form = SignupForm()
    if form.validate_on_submit():
        existing_user = User.query.filter_by(email=form.email.data).first()
        if existing_user is None:
            user = User(
                name=form.name.data,
                email=form.email.data,
                website=form.website.data
            )
            user.set_password(form.password.data)
            db.session.add(user)
            db.session.commit()  # Create new user
            login_user(user)  # Log in as newly created user
            return redirect(url_for('main_bp.dashboard'))
        flash('A user already exists with that email address.')
    return render_template(
        'signup.jinja2',
        title='Create an Account.',
        form=form,
        template='signup-page',
        body="Sign up for a user account."
    )
```
<figcaption>auth.py</figcaption>

In the event of an attempted signup (where POST equals True), we first validate if the user filled out the form correctly via a useful built-in method: `signup_form.validate()`. If any of our form's 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 move on to capture the information they've passed us by running `get()` on get form field. We need to make sure the user isn't trying to sign up with an email which is taken by another user. Thanks to our User model, this is as simple as a single line:

```python
existing_user = User.query.filter_by(email=email).first() 
```
<figcaption>Attempt to fetch a user with the provided email</figcaption>

If `existing_user is None`, we're clear to create a new user. Creating a user is as easy as passing some keyword arguments to our **User** model and generating a password via our model's `set_password()` method:

```python
...

user = User(
    name=form.name.data,
    email=form.email.data,
    website=form.website.data
)
user.set_password(form.password.data)

...
```
<figcaption>Create a user via our model.</figcaption>

Our user is ready to be added to our database, at which point we can login them in:

```python
...

db.session.add(user)
db.session.commit()  # Create new user
login_user(user)  # Log in as newly created user

...
```
<figcaption>Commit our new user record and log the user in.</figcaption>

`login_user()` is a method that comes from the **flask\_login** package that does exactly what it says: magically logs our user in. Flask-Login handles the nuances of of everything this implies behind the scenes... all we need to know is that WE'RE IN!

If everything goes well, the user will finally be redirected to the main application. We handle this via `return redirect(url_for('main_bp.dashboard'))`:

![Handle User Accounts & Authentication in Flask with Flask-Login](https://storage.googleapis.com/hackersandslackers-cdn/2019/07/login.gif)<figcaption>A successful user log in.</figcaption>

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

![Handle User Accounts & Authentication in Flask with Flask-Login](https://storage.googleapis.com/hackersandslackers-cdn/2019/07/signup.gif)<figcaption>Attempting to sign up with an existing email</figcaption>

### Logging In

You'll be happy to hear that signup up was the hard part. Our login route actually shares much of the same logic we've already covered:

```python
...

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    """
    Log-in page for registered users.

    GET requests serve Log-in page.
    POST requests validate and redirect user to dashboard.
    """
    # Bypass if user is logged in
    if current_user.is_authenticated:
        return redirect(url_for('main_bp.dashboard'))

    form = LoginForm()
    # Validate login attempt
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.check_password(password=form.password.data):
            login_user(user)
            next_page = request.args.get('next')
            return redirect(next_page or url_for('main_bp.dashboard'))
        flash('Invalid username/password combination')
        return redirect(url_for('auth_bp.login'))
    return render_template(
        'login.jinja2',
        form=form,
        title='Log in.',
        template='login-page',
        body="Log in with your User account."
    )
```
<figcaption>auth.py</figcaption>

The login route is virtually identical to signing up until we check to see if the user exists. This time around a match results in _success_ as opposed to a failure.

We then use our model's `user.check_password()` method to check the hashed password we created earlier.

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:

![Handle User Accounts & Authentication in Flask with Flask-Login](https://storage.googleapis.com/hackersandslackers-cdn/2019/07/success.gif)<figcaption>A successful log in.</figcaption>

### IMPORTANT: Login Helpers

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

```python
...

@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'))
```
<figcaption>auth.py</figcaption>

`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.

## Logged-in Routes

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** :

```python
"""Logged-in page routes."""
from flask import Blueprint, render_template, redirect, url_for
from flask_login import current_user, login_required


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


@main_bp.route('/', methods=['GET'])
@login_required
def dashboard():
    """Logged-in User Dashboard."""
    return render_template(
        'dashboard.jinja2',
        title='Flask-Login Tutorial.',
        template='dashboard-template',
        current_user=current_user,
        body="You are now logged in!"
    )
```
<figcaption>routes.py</figcaption>

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.

### Logging Out

There's one last route to handle before we say goodbye. Coincidently, it's the route that figuratively allows our users to "say goodbye:"

```python
...
from flask_login import logout_user

...

@main_bp.route("/logout")
@login_required
def logout():
    """User log-out logic."""
    logout_user()
    return redirect(url_for('auth_bp.login'))
```
<figcaption>routes.py</figcaption>
### 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.

{% github https://github.com/toddbirchard/flasklogin-tutorial %}