Adding user authentication to a web application makes it able to tell which user is making a request. It can then return different responses to different users. This is called "personalisation".
This is article 1 of 3 in this series of blog posts, where I'll show you how to work with Flask-Security-Too:
- User authentication in Flask with Flask-Security-Too (this article)
- User email confirmations with Flask-Security-Too
- Customising templates and emails of Flask-Security-Too
To help you understand user authentication, it's useful to remind ourselves of the separation between client and server. If you're developing a Flask application, then you're developing the server. Your clients are browsers or mobile apps making the requests to your application.
For this series, let's assume you are developing a Flask application that uses
render_template to send HTML to your browser clients.
Before learning about Flask-Security-Too, it's probably going to be helpful to learn how to code your own user authentication manually. It's not terribly difficult, and will really aid in understanding how Flask-Security-Too works. We've got two blog posts that I recommend reading in order:
Read those two and work through the examples provided. When you're done, you should have a Flask app that supports user signup and login. That's what we can replace most of our custom logic with Flask-Security-Too!
Add user registration and authentication using Flask-Security-Too
This is the bread and butter of Flask-Security-Too! Here's what you need:
- A database to store user data (username, password, that kind of thing).
- A few configuration options in your Flask app (secret key and password hashing salt).
- To actually initialise Flask-Security-too when you create your
For our database interactions, I will use SQLAlchemy. We don't have any introductory blog posts on using SQLAlchemy, but our free REST APIs e-book does cover it in detail.
In any case, SQLAlchemy is relatively straightforward: you define a Python class which represents a table in your database. When you create new objects of said class, they can be inserted into the database as separate rows. You can also retrieve data from the database in the form of Python objects.
The aim of SQLAlchemy is to simplify working with data using Python by referring to everything as Python objects, rather than writing SQL yourself.
To work with SQLAlchemy using Flask apps, it's common to use the
Let's begin by installing it and
flask-security-too. I'll add this to my
flask flask-sqlalchemy flask-security-too flask-mailman bcrypt python-dotenv
python-dotenv library is added to make it easier to load
.env files when running our Flask app. More on that shortly! The
flask-mailman library is used for sending confirmation emails (in part 2 of this series). The
bcrypt library is used for password hashing.
Install the libraries:
pip install -r requirements.txt
Now let's create the
SQLAlchemy object, which we will use to connect to the database. Inside
database.py, write the following:
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
Next up, let's create our database models. These are the Python classes which represent tables in our database.
models folder, create
auth.py and write the following:
from database import db from flask_security import UserMixin, RoleMixin from sqlalchemy import Boolean, DateTime, Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref class RolesUsers(db.Model): __tablename__ = "roles_users" id = Column(Integer(), primary_key=True) user_id = Column("user_id", Integer(), ForeignKey("user.id")) role_id = Column("role_id", Integer(), ForeignKey("role.id")) class Role(db.Model, RoleMixin): __tablename__ = "role" id = Column(Integer(), primary_key=True) name = Column(String(80), unique=True) description = Column(String(255)) class User(db.Model, UserMixin): __tablename__ = "user" id = Column(Integer(), primary_key=True) email = Column(String(255), unique=True) username = Column(String(255), unique=True, nullable=True) password = Column(String(255), nullable=False) last_login_at = Column(DateTime()) current_login_at = Column(DateTime()) last_login_ip = Column(String(100)) current_login_ip = Column(String(100)) login_count = Column(Integer) active = Column(Boolean()) premium = Column(Boolean()) fs_uniquifier = Column(String(255), unique=True, nullable=False) confirmed_at = Column(DateTime()) roles = relationship( "Role", secondary="roles_users", backref=backref("users", lazy="dynamic") )
As you can see, this defines three tables: one for users, one for roles (similar to permissions), and one for linking users and roles. We won't be using roles in this tutorial, but Flask-Security-Too requires it.
app.py let's create our Flask app and set up Flask-Security-Too:
import os from flask import Flask from flask_security import SQLAlchemySessionUserDatastore, Security from dotenv import load_dotenv from database import db from models.auth import User, Role load_dotenv() app = Flask(__name__) app.config["SECRET_KEY"] = os.environ.get( "SECRET_KEY", "0aedgaii451cef0af8bd6432ec4b317c8999a9f8g77f5f3cb49fb9a8acds51d" ) app.config["SECURITY_PASSWORD_SALT"] = os.environ.get( "SECURITY_PASSWORD_SALT", "ab3d3a0f6984c4f5hkao41509b097a7bd498e903f3c9b2eea667h16", ) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SECURITY_REGISTERABLE"] = True uri = os.getenv("DATABASE_URL") app.config["SQLALCHEMY_DATABASE_URI"] = uri db.init_app(app) user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role) security = Security(app, user_datastore) @app.route("/") def home(): return "Hello, world!"
Now that we've got this, we need to set up our environment variables. At the very least, we need the
DATABASE_URL, to tell our Flask app which database to connect to.
In a new file called
.env, write this:
Now let's create our tables. In the console (having your virtual environment activated), type:
And there, type:
>>> from app import app, db >>> with app.app_context(): db.create_all()
This will create your database using the app configuration, which should create an
instance folder, and
data.db inside it. If later on you delete
instance/data.db, just re-run
flask shell and type those commands in to re-create it.
Now you can run your Flask app! First exit the shell (by pressing CTRL+D) and then type:
Now you can access the login and signup pages that Flask-Security-Too has added to your Flask app for you. For example, going to http://127.0.0.1:5000/login should show you something like this:
If you use the menu in that page to go to the "Register" page (or navigate manually to http://127.0.0.1:5000/register) then you'll see a very similar form, which adds a password confirmation field.
These pages already include error handling and cookie support for the "Remember Me" checkbox.
However, they don't look very good. To fix that, you can either add some CSS code to target the existing elements, or you can code your own login and register pages. We won't talk about that in this blog post, but leave a comment at the end if it's something you'd like me to write about!
Now that we've added user authentication, we can move on to email confirmation in the next article of this series. Thank you for reading, and I'll see you there!
If you'd like to learn more about web development using Flask, consider enrolling in our Web Developer Bootcamp with Flask and Python! It's a complete video course that covers building multiple web apps and deploying them, all using Flask.