Implementing a Role-Based Access Control Service with FastAPI and Postgres

Tsatsu Adogla-Bessa
mPharma Product & Tech Blog
8 min readJun 28, 2021
Photo from Intro to Information Security course on Udacity

According to the official FastAPI documentation, FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. It boasts of features like:

  • Speed: Very high performance, on par with NodeJS and Go (thanks to Starlette and Pydantic). One of the fastest Python frameworks available.
  • Fast to code: Increase the speed to develop features by about 200% to 300%.
  • Fewer bugs: Reduce about 40% of human (developer) induced errors.
  • Intuitive: Great editor support. Everywhere. Less time debugging.
  • Easy: Designed to be easy to use and learn. Less time reading docs.
  • Short: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
  • Robust: Get production-ready code. With automatic interactive documentation.
  • Standards-based: Based on (and fully compatible with) the open standards for APIs: OpenAPI (previously known as Swagger) and JSON Schema.

FastAPI has been out for a little over 2 years and having been impressed with what it has so far at such a young age as compared to other mature frameworks, my aim was to build a basic skeletal authentication framework using FastAPI, which can be plugged into any distributed system with a few tweaks to suit any use case.

Authentication and authorization might sound similar, but they are distinct security processes in the world of identity and access management (IAM). In simple terms, Authentication confirms that users are who they say they are. Authorization on the other hand gives those users permission to access a resource. In secured environments, authorization must always follow authentication. Users should first prove that their identities are genuine before an organization’s system grant them access to the requested resources.

The main tools used for implementation are:

  • fastapi — The web framework
  • docker — For application containerization
  • poetry — For dependency management
  • alembic — For database migrations
  • sqlalchemy — The Object Relation Mapper (ORM)

Application Structure

The structure for this service is based on the recommended structure for organizing large applications in the FastAPI documentation. The high-level overview of the application can be seen below:

The Application Structure

We’ll be focusing on the structure of the app directory above which houses the actual code and authentication logic. Next, we’ll go through an overview of the various modules in the app folder.

The Detailed Structure of the app Directory

  • api — Houses the various routes available in the services and serves as the entry point to service.
api folder contents
  • core — This folder contains the settings and some security utils that are reused throughout the application.
core folder contents
  • crud — As the name suggests, this folder contains the code for performing various database operations related to the models in the app. The CRUD (Create, Read, Update, Delete) operations for each model is organized in their own file.
crud folder contents
  • db — This houses the database configuration and a script for setting up some initial data in the database.
db folder structure
  • models — Contains the model definitions for the database entities
models folder structure
  • schemas — This folder contains pydantic models which FastAPI uses for the definition of data models and payloads to the various APIs.

An Overview of the Various Modules in the app Folder

FastAPI provides several tools to help you deal with security easily, rapidly, in a standard way, and without having to learn all the security specifications. For this application, we’ll be making use of the OAuth2 implementation provided by the framework. The FastAPI documentation has a detailed tutorial that walks you through all the features it provides so this article will mostly provide a high-level overview of the implementation. The code repository will also be linked at the end of the article.

For this post, we will focus on 3 main aspects which are core to all good auth systems and how there are implemented using FastAPI.

  • User management
  • Login APIs
  • Access Control/Authorization

User Management

The application exposes a few endpoints that allow you to manage users on your platform. This is usually the first step in your auth workflow — getting the information on users who is able to use your platform. This is taken further in our use case by allowing users to be able to update their details later on. Some of the endpoints are secured and require specific roles to access them and we’ll look into that later in the article. You can review the endpoints provided below — In the code repository, these files are located in the api and schema directories respectively:

The payloads and response objects for each of the APIs are defined in the schema for the user:

Another component of the user management APIs is the database model which describes the database structure for the user table in the database. Here we make use of sqlalchemy to define how to create the table and fields in PostgreSQL. The user model links a registered user to a role and account which helps with authorization by providing information to let you know which account details the user is allowed to access if they have the required authorization:

Login/Authentication

The next step once we have the user information stored is to authenticate them when they make a request for a resource. We do that using JSON Web Tokens (JWTs). When logging in, the email and password is sent to the backend and compared against what is stored in the database — If the credentials match, a token is returned which will be sent in subsequent requests to access any APIs to ensure the request is coming from an authenticated user. The validity of the token can be configured by changing the value of the ACCESS_TOKEN_EXPIRE_MINUTES in the settings file. Let’s take a look at how it is used in the auth file below:

The most interesting method we want to take note of is the create_access_token below used in the login_access_token route above. This method takes in the payload to be encrypted for the token and the validity duration of the token. The token payload can always be updated to include more/fewer details but it currently stores the user_id, role, and account_id of the user. These details will be sent in each request and so can be decrypted and used to authorize the user and only show the data for their account. It’s worthy to note that just because the token can contain as much data as you want you should be wary of storing sensitive details in the token — So that on the off chance an unscrupulous person gets access to your secret key used to encrypt your token and decrypts any token, they won’t have access to sensitive user information:

Access Control & Authorization

When a subsequent request from a user reaches your application after login, the authorization process involves checking to decrypt the token to authenticate the user and then checking if they have access to the resource they are trying to access. Let’s take a look at the read_users api to examine how this is done:

The read_users api here is marked as a GET using the router.get decorator provided by FastAPI. The function takes in dependencies and query params/path variables that the API expects — In this case, the query_params are the skip and limit which are using for pagination and specifying the number of records to retrieve from the database. The db parameter indicates that this API depends on the database to perform its operation. Now we take a look at the current_user dependency. This parameter makes use of a function which is that is used to authenticate the user and ensure the user has the required role that is permitted to access this API before the request is processed. The Security function provided by FastAPI allows us to make use of OAuth Scopes to determine what the user is allowed to do. In our case we use that to define the role of the user which we saw is stored in the token. A few roles are defined as constants in the role.py file in the constants module and can be expanded to include any roles needed for your use case:

Above, each role defined has a name, which is a description just to give some context as to what that role is intended to do. All scopes supported must be defined and included in the OAuth2PasswordBearer definition which is provided by FastAPI. We can then apply these scopes to the APIs depending on who we would like to have access to them like in our read_users API above.

The last thing we will be taking a look at is the authorization flow which is executed when we attempt to access an authenticated API:

If the API requires any specific roles they will be passed as scopes to the get_current_active_user function. Most of the heavy lifting here is done by this function. If no scopes are required then the token passed in the request is decrypted and the user_id stored in the token is used to retrieve the user. If the user is not found, the token is invalid or expired, then an exception is thrown indicating the error. If scopes are applied, after the token is decrypted, we check to ensure the role in the token matches the role required to access the API. If the user does not have the required role, an unauthorized exception is thrown.

FastAPI is a great option if you’re looking to quickly build light micro-services for your distributed system and this overview can be used as a good basis for a role-based access control authentication service. The functions to authenticate and authorize a user can be replicated in as many micro-services to control access to the APIs exposed in those services. You can check out the complete project on GitHub with instructions on how to get it up and running to test it out in your development environment. It also includes detailed tests for the various modules in the code to ensure everything works as expected!!!

--

--