25 Aug Flask login with Flask-JWT-Extended and SQLAlchemy and MongoDb token storages
Aug 25, 2022 – 8 min read
Intro
In this article I will speak about how to implement user authorization workflow with Flask-JWT-Extended that includes registering a new user, login, logout, refresh token, revoke a list of tokens as well as accessing pages that require authorization.
- Flask-JWT-Extended
- flask_sqlalchemy OR pymongo: Used to store the sessions and the user. Using a storage for storing the session is not required by Flask-JWT-Extended but we’ll use it in order to provide additional features – get the current user tokens and revoke a list of tokens.
How does Flask-JWT-Extended work?
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
Local storage
We’ll use SQLAlchemy and MongoDb to store the uses and the user’s sessions.
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, logout, refresh token and revoke a list of tokens. That includes API endpoints, validation of data received from the user, 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.
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, logout, refresh token and revoke a list of tokens. Also, it loads an instance of Flask-JWT-Extended that will be initialised with the Flask app later
Loads a db instance of SQLAlchemy and an instance of bcrypt used to encrypt the password when registering a new user or logging in.
If we decide to use MongoDb that part of the code would look like this.
Load the environment variables.
Create an instance of the Flask app and configure the application.
The purpose of the configurations is the following:
- JWT_SECRET_KEY. A secret key required by JWT to encode the token sent to the Front-end.
- JWT_COOKIE_SECURE. A configuration telling JWT to accept requests via SSL or not.
- JWT_ACCESS_TOKEN_EXPIRES. Expiration time of the access token – in seconds.
- JWT_REFRESH_TOKEN_EXPIRES. Expiration time of the refresh token – in seconds.
Register the blueprints of all API endpoints.
Initialise the following services – db storage, bcrypt and jwt. 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 libraries necessary for the work of the SQLAlchemy model.
Load the environment variables and initialise SQLAlchemy database and bcrypt used to has the password sent by the user.
The User model uses 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 of the User model 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.
- get_sessions. Get a list of user’s sessions.
The rest of the code is the Session model using SQLAlchemy to manage the sessions of the user.
Here we define the name of the table where we store the sessions to be `session`. Also, we define the properties of the session – id, user_id, jti_access, jti_refresh and created_at.
The rest of the code contains the following methods:
- add. Add a new session.
- get_by_jti_access. Get the session associated with jti_access.
- get_by_jti_refresh. Get the session associated with jti_refresh.
- refresh. Refresh a session.
- delete. Delete a session.
- get_user_sessions. Get the sessions of a 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, login_validator and revoke_tokens_validator are used to validate the data coming from the Front-end when registering a new user, logging in an exiting one and also to check the list of expected tokens when revoking tokens.
auth/appAuth.py
Load required libraries.
Depending on which storage we choose to use, we have to import User model from the corresponding place.
or
Next:
Initialise Flask-JWT-Extended.
Create a blueprint for the Authorization API endpoints.
JWT decorator used to get the user id when requesting an endpoint that requires JWT token.
JWT decorator used to get the user when requesting an endpoint that requires JWT token.
JWT decorator that checks if the token – access or refresh – exists in the storage.
API response when the user is not authorized.
The rest of the code contains three API endpoints:
- register. Handle the process of registering a new user, creating new access and refresh tokens, stores them in the storage and returns them to the Front-end.
- login. Handle the process of logging in an existing user, creating new access and refresh tokens, stores them in the storage and returns them to the Front-end.
- logout. Handle the process of logging out an existing user. It deletes the token from the storage.
- refresh. Refresh a token.
- revoke_tokens. Revokes a list of tokens.
appExample.py
Load required libraries.
Depending on the storage we use, we can import the User model in one of the following ways.
or
Next:
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
Copy `.env.example` and create a new file `.env`. Inside the file set all required values.
Open files `app.py,
`appExample.py` and `auth/appAuth.py`. Depending on if you use SQLAlchemy or MongoDB comment/uncomment the corresponding lines of code in order to switch to the desired database.
Installation
Create and activate a virtual environment and install the dependencies:
If you use SQLAlchemy also run:
Run
Check The Storage
If you use SQLAlchemy use the commands bellow.
To connect the storage, in the Terminal type:
To get a list of available tables:
To list the records in a particular table:
To exit the storage:
If you use MongoDb install Mongo Compass on your local machine, open it and connect to localhost.
Stop
On Mac press Control + C and after that deactivate the virtual environment via:
Additional readings
If you need assistance with your Flask application or you have suggestions for improving the content, don’t hesitate to contact me.