Python Friday #244: Integrate FastAPI Users Into the To-Do Application

A few weeks ago, we explored two approaches to add authentication to our FastAPI applications. In this post we go a different way and integrate a package that does the heavy lifting for us. I decided to go with FastAPI Users, even when it takes a big step to integrate it.

This post is part of my journey to learn Python. You find the code for this post in my PythonFriday repository on GitHub.

 

Installation

We can install FastAPI Users for SQLAlchemy and OAuth with this command:

 

Add the user table

FastAPI Users comes with everything we need, and one of the most obvious things is a table for our users. We can put these few lines to our data/entities.py file to get a basic user table:

If you want to add more fields, you can do that here. For the first round I suggest you start with the built-in parts and only add as soon as you have a working solution.

 

Create a migration file

With our new table in the entities.py file, we can create a migration file for Alembic with this command:

The generated migration file should look like this:

If all looks good and you added the missing imports (should that be necessary), we can apply the migration with this command:

Do not forget to copy the migrated database to our test template:

 

Refactor the dependencies.py file

We need to use the same database file for the FastAPI Users configuration as we do for our data store. But as it stands, we only have a method to get the datastore. We can fix this with a small refactoring:

We can run all our existing tests and they should pass.

 

Add an authentication.py file

I put all the authentication related code into the authentication.py file. There is a lot going on to glue this plug-in into our application. The main points you should know about are these:

  • The UserManager class contains all the logic to access the database, works with our User table and uses a GUID as the identifier.
  • The methods get_async_session() and get_user_db() glue our existing infrastructure together to finally use the get_user_manager() method to initialise the UserManager class.
  • AuthenticationBackend specifies what we want to use for our authentication (we use JWT with bearer token).
  • The fastapi_users variable is our entry to the infrastructure (like routers) thar are part of the FastAPI Users package.
  • We can inject current_active_user into our methods when we want the user to be authenticated.
  • We put the SECRET_KEY_ENV in a .env file as we did in this post. Make sure that you put that file one directory above the extended_todo folder!

The code to do all that looks like this:

 

Prepare the endpoint tests

So far, we added all the new parts, but we did not integrate them in our application. Before we put everything together, we need to change our tests. If we are not careful, we can mess up our tests and create race conditions between our test files. To prevent that, we need to create a new fixture that we use for our existing endpoint tests where we assume we have a logged-in user:

As the next step, we must use this fixture in all tests inside test_todo.py and switch from client to test_client:

We can run all our tests and they should pass. We already mock a method we not yet use, but that will change with this new test:

This new test fails because we do not yet have the endpoint it tries to call. Let us fix that.

 

Wire-up FastAPI Users in our application

We can now add the 5 routers from FastAPI Users into our application and create a /about/me endpoint to read the email address from the current user:

The FastAPI Users endpoint use these Pydantic models to send data around:

This new code was the missing part for our failing test. We can now rerun all tests and they should pass.

 

Protect an endpoint

The authentication middleware only helps us when we use it in our application. We can now enforce that the user is logged-in before they can create a new task, update an existing one or delete it. All we need to do is to inject the current_active_user method from our authentication.py file into the endpoint:

We can rerun our tests and they will pass. That is because our mock simulates a logged-in user. If we start the application and try to add a task without logging in, we get a 401 error. Let us add some tests to make sure that the login works.

 

Add authentication tests

The whole logic to log in comes from FastAPI Users. So far, we have no tests to check if the behaviour works as we expect. We can fix that with this set of tests:

In the test_client we use in this file we are not logged in. Therefore, our attempt to add a task will fail. The same should happen when we call the /about/me endpoint without logging in.

The test test_user_can_register() shows us how we can add a new user. But that will not be enough to work with the API. To see what it takes to log in and use the bearer token, we have the test_user_can_login_and_sees_email_in_about_me() test.

We can now run all the tests and they should pass.

 

Attention: Uvicorn and .env

If you run the application in Uvicorn, you run it from one directory above the extended_todo folder. If you have the .env file with the SECRET_KEY_ENV variable inside the extended_todo folder, you will get an internal server error with the message “Expected a string value“. Good luck with debugging that problem.

The solution to that error is to add the .env file in the parent directory of the extended_todo folder. Then everything works as expected.

 

Next

Integrating FastAPI Users in an application is not a quick task. As this post shows, there are many parts we need to put together before everything works. But as soon as this is done, we can add OAuth logins like Google or Okta to our application without much additional effort.

Next week we put our little to-do application into a Docker container and make it ready for production.

3 thoughts on “Python Friday #244: Integrate FastAPI Users Into the To-Do Application”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.