24 Aug Flask-Login with SQLAlchemy and MongoDb token storages
Aug 24, 2022 – 9 min read
Intro
In this article I will speak about how to implement user authorization workflow – for Flask using Flask-Login – that includes registering a new user, login, logout as well as accessing pages that require authorization.
- Flask-Login: provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time. It will: Store the active user’s ID in the Flask Session, and let you easily log them in and out.
- Flask-Session: is an extension for Flask that adds support for Server-side Session to your application.
- flask_sqlalchemy OR pymongo: Storage used to store the sessions and the user.
How do Flask-Login and Flask-Session work?
Once the user is logged in, Flask-Login handles the creation of a session – stored in a Cookie – on the user’s machine. When requesting Flask, Flask-Login gets the session for you and checks if the user exists in the local storage. This is how it knows if the user is logged or not.
The downside of this approach is that the session is stored in a cookie which is vulnerable to hacks. That’s the reason to use Flask-Session as well.
Flask-Session handles the storage of the session on the server side where the cookie mentioned earlier stores a key pointing to that session. That provides greater security. In that particular case the weakness of your application will depend on how secured is your server.
Local storage
Since we’re going to implement login via email and password using Flask-Login and Flask-Session we need a storage on the server to store the users and the sessions. In this project we’ll use SQLAlchemy and MongoDb.
Please note that we use pymongo version 3.11.0 because it still supports methods `update` and `insert` used by flask-session. Unfortunately for newer versions of pymongo these methods are not supported which causes issues when using Flask-Session with MongoDB storage.
If you want to store the users and the sessions in MongoDb, please install Mongo Compass on your local machine to access the database.
Code and folder structure
The Github repository of the project is located here. Download it on your machine and let’s get started.
The structure of the project is the following:
The responsibilities of the files/folders is the following:
- auth – contains all files responsible for registering a new user, login and logout. That includes API endpoints, validation of data, SQLAlchemy and MongoDb models.
- .env – environment variables required by the application
- app.py – setting variables required by the application, registering blueprints (API endpoints), initialising services and running the Flask app.
- appExample.py – contains two API endpoints. One that requires user authorization and one that does not.
- install.py – we run that script only once to build SQlAlchemy database.
- requirements.txt – contains python packages required by the application
API endpoints
The application supports the following endpoints:
Code review
Let’s review the code and give detailed explanation of what it does.
install.py
This code loads the Flask app and SQLAlchemy model and builds the SQLAlchemy database – tables user and sessions. After the build is completed you will find database.db file in the main folder of the project.
We need that code only if we choose to store users and session in SQLAlchemy storage.
app.py
Load libraries required to initialise a Flask application, load and access environment variables from .env.
Loads appExample blueprint related to the API endpoints of the home page and the profile page.
Loads appAuth blueprint related to the API endpoints – register, login and logout. Also, it loads:
- session – an instance of Flask-Session.
- login_manager – an instance of LoginManager. The login manager contains the code that lets your application and Flask-Login work together, such as how to load a user from an ID, where to send users when they need to log in, and the like.
Loads a db instance of SQLAlchemy and an instance of bcrypt used to encrypt the password when registering a new user or logging in with an existing one. The last line of the code loads the environment variables from .env.
If we decide to use MongoDb that part of the code would look like this.
Client is an instance of the MongoDb.
Create an instance of the Flask app and configure the application.
The purpose of the configurations is the following:
- SECRET_KEY. Each Flask web application contains a secret key which used to sign session cookies for protection against cookie data tampering. It’s very important that an attacker doesn’t know the value of this secret key.
- PERMANENT_SESSION_LIFETIME. After creating a new user session this is the period – in seconds – after which the session is going to expire.
- SESSION_TYPE. The type of storage Flask-Session is going to use to store the sessions.
- SESSION_SQLALCHEMY. An instance of the storage. In this particular case this is SQLAlchemy.
- SQLALCHEMY_DATABASE_URI. URL to the database (i.e. sqlite:///database.db).
- SQLALCHEMY_TRACK_MODIFICATIONS. Suppress notification in the terminal initiated by SQLAlchemy.
If we decide to use MongoDb that part of the code would look like this.
Where:
- SESSION_MONGODB. An instance of the storage. In this particular case this is MongoDb.
- SESSION_MONGODB_DB. URL to the database (i.e. mongodb://localhost:27017).
- SESSION_MONGODB_COLLECT. Name of the collection where we’ll store the sessions.
Register the blueprints of all API endpoints.
Initialise the following services – db storage, bcrypt, session and login_manager. Also, please note that if we choose to use MongoDb, initialising the db storage is not necessary.
If the application tries to return HTTP error codes 404 or 405, return json with HTTP code 404.
Run the Flask application when executing the current file. The configuration – port and debug mode – depends on the configuration in .env file.
auth/models/sqlaModels.py
This is the model we use when using SQLAlchemy as a storage.
Load SQLAlchemy libraries.
Load Bcrypt used to encrypt the password when registering a new user or logging an existing one. Load UserMixin required by Flask-Login when loading an instance of the user. Also, load SQLAlchemy.
Instantiate SQLAlchemy and Bcrypt. These instances are used in app.py – as we already noticed – to initialise them.
The rest of the code is the User model – inheriting UserMixin – using SQLAlchemy to register, log in and get the user.
The properties id, email, username and password are going to be used when building the database. These are the properties of table user in SQLAlchemy storage.
The rest of the code contains the following methods:
- get_by_id. Get the user by id.
- register. register a new user.
- get_by_email. Get the user by email
- get_by_username. Get the user by username. In our case the username is identical to the email but it is up to you to change that depending on your needs.
- get_verified. Get a user by email or username and password. If the user does not exist return None. This method is used when logging in the user.
auth/models/mongodbModels.py
This is the model we use when using MongoDb as a storage.
The main difference between sqlaModels.py and mongodbModels.py is the way we initialise the database.
auth/validators.py
The validators register_validator and login_validator are used to validate the data coming from the Front-end when registering a new user or logging in an exiting one.
auth/appAuth.py
Load Flask-Login, Flask-Session, Flask libraries and validators.
Depending on which storage we choose to use, we have to import User model from the corresponding place.
or
Next:
Instantiate Flask-Session and create a blueprint for the Authorization API endpoints.
Instantiate the login manager.
This code is responsible for loading the user from the storage every time the Front-end requests an API endpoint requiring the user to be logged in. An important note here is that every time we request an API endpoint requiring the user to be logged in, Flask-Session is going to prolong the expiration datetime of the session.
This code is responsible for returning a proper response when the user is not logged in but it is required to be authorised.
The rest of the code contains three API endpoints:
- register. Handle the process of registering a new user.
- login. Handle the process of logging in an existing user. Please note that we use login_user() provided by Flask-Login to log in the user and create a new session.
- logout. Handle the process of logging out an existing user. Please note that we use logout_user() provided by Flask-Login to log out the user.
appExample.py
Load Flask libraries and current_user and login_required provided by Flask-Login.
- login_required. A decorator we use every time we’d like a particular API endpoint to be accessible only by authorized users.
- current_user. When then one who requests the API is authorized, current_user is the User object containing the user’s data.
Here we create a blueprint for the API endpoints – home and profile. The endpoint home does not required the user to be authorized and profile requires the user to be authorized. The reason we have them is to test the whole workflow.
Let’s move ahead!
Configuration of the project
Installation
Run
Manual test
- / – page is accessible
- /profile – page is not accessible, authorization is required
- /v1/auth/register
- /v1/auth/login
- /profile – page is accessible
- /v1/auth/logout
- /profile – page is not accessible, authorization is required
Check the storage
If you use MongoDb install Mongo Compass on your local machine, open it and connect to localhost.
Stop
Additional readings
If you need assistance with your Flask application or you have suggestions for improving the content, don’t hesitate to contact me.