Flask TodoMVC: Login

This is the seventh article in the Flask TodoMVC tutorial, a series that
creates a Backbone.js backend with Flask for the TodoMVC app.
In this article, we will add user authentication using the Flask-Security
extension, focusing on setup and login. In doing so, we will define models for
users and roles and discuss SQLAlchemy relationships.

Introduction

Flask-Security is an extension that quickly adds authentication,
authorization, registration and password recovery to your app. It combines
several other popular Flask extensions, including:
Flask-Login for authentication, Flask-Principal for role based authorization,
Flask-WTF for form validation, Flask-Mail for sending registration, confirmation and
reset password emails and Flask-Script for command line user management scripts.

It also supports password encryption using passlib and secure key generation for token
based authentication, optional account activation, and password recovery using itsdangerous.

If that weren't enough, it adds a data store abstraction layer to persist and query your users and
roles and automatically track user login activity and confirmation. Current supported data stores include
Flask-SQLAlchemy, Flask-MongoEngine and Flask-Peewee, but you can easily create a
custom store if so desired. (How about dataset?)

Flask-Security makes things easy to get started, but may seem too feature rich or opinionated
for some use cases. Like all Flask extensions, take what you want and leave the rest. If, at some point,
you decide Flask-Security conventions are getting in the way, at the very least it's a good example
of how to combine several common extensions and provide security to your app.
I've found working with Flask-Security a very pleasant way to add some of the most important features
common to many apps. I'd highly recommend it.

OK, enough talk. Let's get this thing installed.

$ pip install Flask-Security py-bcrypt

Flask-Security supports password encryption but requires a backend to define the algorithm. We will
use py-bcrypt so we install that as well.

We will explain all of these imports as we progress through this article. Let's
begin by defining models for users and roles.

Models

Flask-Security requires the addition of two models: users and roles. The user model stores
login credential and optional audit information (create time, last login time, IP address, etc.)
for all individuals who have access to your app. A role can be thought of as a group of users
that may have elevated privileges. A user with the "admin" role would have access to the
administration section of an app, a "manager" could view financial reports, etc. Roles are
usually defined at the application level.

You may need to provide finer grain access control, e.g. allow a manager to track time for personnel
within her own department, but not have access to other departments. In this case, you would
add additional models that are scoped to your department models and provide authorization
with finer control. If you find yourself wanting more granular control, take a closer look at the
Flask-Principal extension, the extension that Flask-Security uses to provide role based
authorization.

We may dive further into authorization in the future, but today we are going
to focus on setting up Flask-Security and adding authentication to our todo list page.

Most of this should look familiar if you followed the previous article. Roles belong
to the roles table. We added a primary key id, role name (e.g. 'admin') and a human readable
description. We defined the name column as unique as we will identify roles by name in
our code. This is one of several options supported by SQLAlchemy when defining columns.

We inherit from both db.Model and the RoleMixin we imported from Flask-Security. This may
seem a little strange if you haven't seen it before. We won't get into the details, or debate
the merits and pitfalls of multiple inheritance, but I do believe that mixins are a valid use
case. They allow you to append functionality to an existing class hierarchy. They are also
sometimes used as a "marker interface" to identify objects that could, for example, be serialized
in a certain way.

In this case, consider db.Model your base class and the RoleMixin a mechanism to mixin
convenience methods defined outside the model hierarchy. Currently Flask-Security only
mixes in __eq__ and __neq__ to define role equality based on the unique role name.

Again, most of this is familiar. We identify the users table, define an id primary key,
add columns for the email address and password and an active flag.

When using Flask-Security,
users are identified by email address. The email address is the login name.
This same email address is used to send forgot password and confirmation emails, if those
features are enabled. Only active users that identify the correct email address and password
will be allowed to login.

Note that you can add custom columns at will to your user model. You may, for example,
want to include contact or organization hierarchy details. Flask-Security also supports
optional features to confirm or track users. If you would like to enable these features,
be sure to include the additional columns.

We also inherit the UserMixin imported from Flask-Security. This currently mixes in all methods
required by the user class of Flask-Login in addition to has_role for checking whether a
user has a role identified by name or instance, and a get_auth_token method to be used for
optional token based authentication.

Relationships

As we discussed, there is a relationship between users and roles. There may be more than one
user with the same role, e.g. you could have multiple ninjas, rockstars and hipsters in your
Web 3.0 startup. A user identifies individuals. Each individual could have zero or more roles.
You, of course, are a ninja rockstar, but not all that hip (you just know what you're talking about,
that's all), so you would have two roles.

So a user could have several roles, and a role could be associated with multiple users. This type
of relationship is known as a "Many to Many" and requires a join table that includes foreign keys
to identify each side of the relationship. Since we don't want to treat this table as its own
entity (modeled association), we can define the table directly.

This creates the join table necessary to create a Many to Many relationship between users
and roles. We call it roles_users to reflect the relationship. (We could have called it
users_roles as well, but this distinction is mostly arbitrary.) We then identify two integer columns
with foreign keys to the appropriate column and table. Notice that here we used the table
and column name, not the model name.

Other relationships include "Many to One" or "One to Many" (depending on which side you consider
holds the relationship). If you wanted to store multiple addresses for your users, and ignore or don't
care that two users might share the same address, your address model would have a "Many to One"
relationship with your user, and your user model would have a "One to Many" relationship with
addresses.

A less common relationship is "One to One". You could use this, for example, to associate a
user object with a contact model. The contact could contain all contact information that make
up an address book. The user could be associated with an existing contact by defining a contact_id
on the user model. In this way, you could associate users to your address book, but not require
all contacts to have login credentials.

This defines the relationship between users and roles. We are adding a roles attribute
to our User model. Accessing this attribute will list all roles associated with the user.
The association is to the Role model, which we identify as the first argument. We specify
our join table defined above to setup the Many to Many relationship.

Finally we specify a backref named users on the Roles model. This will add an attribute
to any role instance that retrieves all users with the given role through a users attribute.
Since we specify the backref relationship as dynamic, we will not get a list of roles, but a
SQLAlchemy query that we can further filter. We could find, for example, all enabled managers by
first retrieving the role and then filtering by active, i.e. manager_role.filter_by(active=True).all().

You could also specify the relationship itself as dynamic, not just the backref. See the
SQLAlchemy documentation on dynamic relationships for further information.

Setup

With our models defined, we are finally ready to initialize the Flask-Security extension.
The extension requires an app and a data store. Lucky for us, Flask-Security has built
in support for Flask-SQLAlchemy.

When we login to our app, Flask-Security will store the user id in a session so subsequent
requests will not require a login. Flask stores all session data in a signed cookie, which
requires SECRET_KEY to be configured. It is a good idea to use a random string for your
secret key and change it for different installations. This example used os.urandom(24) to
generate a random string. Run on your app to create your own.

We also setup password encryption using bcrypt and set the salt to the secret key. We do not
want to store the passwords in plain text in our user table. If anyone gains access to our
database, we do not want to leak passwords associated with a user email address, especially since
too many users reuse the same password across multiple sites. Since passwords are encrypted, we
will never know what password the user provided simply by looking at the database. Specifying
the password hash algorithm and salt is all we need to setup encryption using Flask-Security.

Before we require login, we should setup a user that can login. We will add user registration
in a future article, but for now, let's create a single user in init_db.

Querying the user model for the first entry will return a single user or None if it
does not exist. Remember, db.create_all will create any tables that do not exist, so
our users and roles tables will be created the first time we start the app. If we don't
yet have a user, we create one using a convenience method on the user data store.
We need to commit our session since we made changes to the database.

We run the method within a Flask application context. We are using encrypt_password
from Flask-Security which requires a context to be setup. An application context is
created by Flask automatically for each request and proxies are setup to be valid within
this context. Since we are calling init_db standalone, we explicitly create an app context.
We could alternatively use @app.before_first_request.

Requiring login

Now that we have Flask-Security initialized, the hard work is done. All we have
left to do is enforce login by using the login_required decorator.

@app.route('/')@login_requireddefindex():todos=Todo.query.all()...

Go ahead and start your app and navigate to localhost:8000. You should be redirected to
the login page. Login with the credentials you setup in init_db to visit the todo list.

At this point we should decorate all API endpoints with @login_required because, without
that protection, anyone could still modify our database by requests to the Todo API. This
is exactly what our tests are doing. We are going to delay protecting the API and fixing
the tests for a future article.

Our login page isn't very pretty. If you want to logout, currently you need to visit
localhost:8000/logout. We will address these issues in a future article.

Conclusion

In this article, we added user authentication to our Todo list using Flask-Security. While
doing this, we added additional models for users and roles and discussed SQLAlchemy relationships.

Our app is getting a little cluttered, in the next article we will clean up a little and
focus on modularization.

That completes our integration of authentication from Flask-Security. If you made it this far you
should follow me on Twitter and GitHub.

The code is available on GitHub with tag login or compared to previous article.