Adding Authentication to Your FARM Stack App
Aaron Bassett, Mark Smith7 min read • Published Feb 12, 2022 • Updated Apr 02, 2024
Rate this tutorial
If you have not read my Introduction to FARM stack tutorial, I would urge you to do that now and then come back. This guide assumes you have already read and understood the previous article so some things might be confusing or opaque if you have not.
An important part of many web applications is user management, which can be complex with lots of different scenarios to cover: registration, logging in, logging out, password resets, protected routes, and so on. In this tutorial, we will look at how you can integrate the FastAPI Users package into your FARM stack.
- Python 3.9.0
- A MongoDB Atlas cluster. Follow the "Get Started with Atlas" guide to create your account and MongoDB cluster. Keep a note of your database username, password, and connection string as you will need those later.
- A MongoDB Realm App connected to your cluster. Follow the "Create a Realm App (Realm UI)" guide and make a note of your Realm App ID.
Let's begin by cloning the sample code source from GitHub
Once you have cloned the repository, you will need to install the dependencies. I always recommend that you install all Python dependencies in a virtualenv for the project. Before running pip, ensure your virtualenv is active. The requirements.txt file is within the back end folder.
It may take a few moments to download and install your dependencies. This is normal, especially if you have not installed a particular package before.
You'll need two new configuration values for this tutorial. To get them, log into Atlas and create a new Realm App by selecting the Realm tab at the top of the page, and then clicking on "Create a New App" on the top-right of the page.
Configure the Realm app to connect to your existing cluster:
You should see your Realm app's ID at the top of the page. Copy it and keep it somewhere safe. It will be used for your application's
REALM_APP_ID
value.Click on the "Authentication" option on the left-hand side of the page. Then select the "Edit" button next to "Custom JWT Authentication". Ensure the first option, "Provider Enabled" is set to "On". Check that the Signing Algorithm is set to "HS256". Now you need to create a signing key, which is just a set of 32 random bytes. Fortunately, Python has a quick way to securely create random bytes! In your console, run the following:
Running that line of code will print out some random characters to the console. Type "signing_key" into the "Signing Key (Secret Name)" text box and then click "Create 'signing_key'" in the menu that appears underneath. A new text box will appear for the actual key bytes. Paste in the random bytes you generated above. Keep the random bytes safe for the moment. You'll need them for your application's "JWT_SECRET_KEY" configuration value.
Now you have all your configuration values, you need to set the following environment variables (make sure that you substitute your actual credentials).
Set these values appropriately for your environment, ensuring that
REALM_APP_ID
and JWT_SECRET_KEY
use the values from above. Remember, anytime you start a new terminal session, you will need to set these environment variables again. I use direnv to make this process easier. Storing and loading these values from a .env file is another popular alternative.The final step is to start your FastAPI server.
You may notice that we now have a lot more endpoints than we did in the FARM stack Intro. These routes are all provided by the FastAPI
Users
package. I have also updated the todo app routes so that they are protected. This means that you can no longer access these routes, unless you are logged in.If you try to access the
List Tasks
route, for example, it will fail with a 401 Unauthorized error. In order to access any of the todo app routes, we need to first register as a new user and then authenticate. Try this now. Use the /auth/register
and /auth/jwt/login
routes to create and authenticate as a new user. Once you are successfully logged in, try accessing the List Tasks
route again. It should now grant you access and return an HTTP status of 200. Use the Atlas UI to check the new farmstack.users
collection and you'll see that there's now a document for your new user.The routes and models for our users are within the
/backend/apps/user
folder. Lets walk through what it contains.id
(UUID4
) – Unique identifier of the user. Default to a UUID4.email
(str
) – Email of the user. Validated byemail-validator
.is_active
(bool
) – Whether or not the user is active. If not, login and forgot password requests will be denied. Default toTrue
.is_superuser
(bool
) – Whether or not the user is a superuser. Useful to implement administration logic. Default toFalse
.
You can use these as-is for your User models, or extend them with whatever additional properties you require. I'm using them as-is for this example.
The FastAPI Users routes can be broken down into four sections:
- Registration
- Authentication
- Password Reset
- User CRUD (Create, Read, Update, Delete)
You can read a detailed description of each of the routes in the FastAPI Users' documentation, but there are a few interesting things to note in this code.
These functions are called after a new user registers and after the forgotten password endpoint is triggered.
The
on_after_register
is a convenience function allowing you to send a welcome email, add the user to your CRM, notify a Slack channel, and so on.The
on_after_forgot_password
is where you would send the password reset token to the user, most likely via email. The FastAPI Users package does not send the token to the user for you. You must do that here yourself.In order to create our routes we need access to the
fastapi_users
object, which is part of our app
object. Because app is defined in main.py
, and main.py
imports these routers, we wrap them within a get_users_router
function to avoid creating a cyclic import.Currently, Realm's user management functionality is only supported in the various JavaScript SDKs. However, Realm does support custom JWTs for authentication, allowing you to use the over the wire protocol support in the Python drivers to interact with some Realm services.
The available Realm services, as well as how you would interact with them via the Python driver, are out of scope for this tutorial, but you can read more in the documentation for Users & Authentication, Custom JWT Authentication, and MongoDB Wire Protocol.
Realm expects the custom JWT tokens to be structured in a certain way. To ensure the JWT tokens we generate with FastAPI Users are structured correctly, within
backend/apps/user/auth.py
we define MongoDBRealmJWTAuthentication
which inherits from the FastAPI Users' CookieAuthentication
class.Most of the authentication code stays the same. However we define a new
_generate_token
method which includes the additional data Realm expects.Now we have our user models, routers, and JWT token ready, we can modify the todo routes to restrict access only to authenticated and active users.
The todo app routers are defined in
backend/apps/todo/routers.py
and are almost identical to those found in the Introducing FARM Stack tutorial, with one addition. Each router now depends upon app.fastapi_users.get_current_active_user
.Because we have declared this as a dependency, if an unauthenticated or inactive user attempts to access any of these URLs, they will be denied. This does mean, however, that our todo app routers now must also have access to the app object, so as we did with the user routers we wrap it in a function to avoid cyclic imports.
The FastAPI app is defined within
backend/main.py
. This is the entry point to our FastAPI server and has been quite heavily modified from the example in the previous FARM stack tutorial, so let's go through it section by section.This function is called whenever our FastAPI application starts. Here, we connect to our MongoDB database, configure FastAPI Users, and include our routers. Your application won't start receiving requests until this event handler has completed.
The shutdown event handler does not change. It is still responsible for closing the connection to our database.
In this tutorial we have covered one of the ways you can add user authentication to your FARM stack application. There are several other packages available which you might also want to try. You can find several of them in the awesome FastAPI list.
If you have questions, please head to our developer community website where the MongoDB engineers and the MongoDB community will help you build your next big idea with MongoDB.