Auth Dependencies in FastAPI

UPDATED ON:

undraw svg category

Welcome to Part 8 of Up and Running with FastAPI. If you missed part 7, you can find it here .

This series is focused on building a full-stack application with the FastAPI framework. The app allows users to post requests to have their residence cleaned, and other users can select a cleaning project for a given hourly rate.

Up And Running With FastAPI

Last post we removed code that saved our users’ password in plaintext, and replaced it with a more production-ready authentication system. We implemented an AuthService class that hashes users’ passwords and returns JSON Web Tokens that we’ll use to allow members to interact with authenticated API endpoints. In this post we’ll take that a step further and start building out some of those authenticated endpoints. By the end of the article, users should be able to log in and request protected information about their own accounts.

Login Flow

Let’s talk about the login mechanism we’ll be expecting our application to follow here. The FastAPI docs have a section on security, which discusses this in greater detail.

The docs outline a general login flow that we’ll attempt to architect.

  1. The user types her username and password in the frontend, and hits Enter.
  2. The frontend (running in the user’s browser) sends that username and password to a specific URL in our API.
  3. The API checks that username and password, and responds with a “token”.
    • A “token” is just a string with some content that we can use later to verify this user.
    • Normally, a token is set to expire after some time.
    • So, the user will have to login again at some point later.
    • And if the token is stolen, the risk is less. It is not like a permanent key that will work forever (in most of the cases).
  4. The frontend stores that token temporarily somewhere.
  5. The user clicks in the frontend to go to another section of the frontend web app.
  6. The frontend needs to fetch some more data from the API.
    • But it needs authentication for that specific endpoint.
    • So, to authenticate with our API, it sends a header Authorization with a value of Bearer plus the token.
    • If the token contains foobar, the content of the Authorization header would be: Bearer foobar.

As before, we’ll write some tests and then make them pass.

TDD for a Login Endpoint

Let’s open up our test_users.py file and add a new class.

tests/test_users.py

We’ve written enough tests at this point that we’re probably pretty familiar with what’s going on. Even so let’s talk about what we’re expecting.

First things first, a user sends their email and password to our login point as form data and not JSON (using the “username” and “password” keys outlined by FastAPI in the docs here ). Accordingly, in both tests we’re updating the content-type header to accept form data instead of JSON. This matches the same OAuth2 specs mentioned before and is required to use some of the FastAPI imports we’ll discuss in a minute.

For our first test, we check to make sure the response is valid, that the response includes a valid token, and that the token encodes the correct username and email for our test user. In the second test, we make sure that if any of that is incorrect, we get the proper status code and no token.

Notice in both tests how we’re not using the json parameter for sending data with our httpx client. Instead we use the data parameter. This is intentional and is how httpx expects form data to be sent in our client.

Now all we need to do is implement our login endpoint!

The LOGIN Endpoint

We might assume that this part should be relatively simple, since we already implemented user registration. However, this section is still pretty involved, so strap in.

Let’s start with the login route.

api/routes/users.py

The login route is very similar to the registration route, except this time we’re using the authenticate_user method on our UsersRepository to check the validity of the submitted login credentials. If we don’t get a user, we raise an HTTP_401_UNAUTHORIZED exception. Otherwise, we send back an access token with the user encoded in it.

Let’s work on the authenticate_user method next.

db/repositories/users.py

Simple enough. We check to make sure that a user exists in our database with the submitted email. If not, we return None. Then we use our auth service to hash the user’s password with the stored salt and verify that it matches the hashed password we have stored for that user. If all things turn out alright, we return the user.

Run the tests and watch them all…fail? We’re seeing 400 error responses for every failed test as well - what’s up with that? Parsing the body for FastAPI’s OAuth2PasswordRequestForm requires the python-multipart library that we haven’t installed yet. A nice clue is provided by the FastAPI docs , but for those who haven’t read that section - we’ll dig in here.

To diagnose that this is, in fact, the problem, we open up our interactive documentation at localhost:8000/docs and try out the login route. Type in a username and password, then hit execute. If we switch to our terminal running Docker, we should see a log statement that looks like:

So, let’s set that up.

requirements.txt

Stop the running container and then make sure to rebuild a new one.

Once the container has finished builing, go ahead and run all tests in the test_users.py file like so:

And just like that, they’re passing. Fantastic.

So now that users can sign up for our app and login, let’s add a protected route that sends them information about themselves.

Auth Dependencies

To protect an endpoint in FastAPI, we’ll need to create a few auth dependencies meant to handle much of the grunt work. We’ll start by taking a page right out of FastAPI’s documentation and defining a /me/ route that returns the currently signed in user. That route will use our auth dependencies to ensure a token is included in the Authorization header and that the token is valid. If it is, we’ll return the user encoded in the token.

Let’s make a new fixture that we’ll use to make authorized requests.

Open up the tests/conftest.py file.

tests/conftest.py

Our authorized_client fixture is the first example we’ve seen of how an access token will be used when making authorized requests to our API. We start by creating an access token for our test_user as we’ve seen before. Then, we take that token, prepend the appropriate prefix (“Bearer ”) and add it to the Authorization header of our client fixture.

Now, whenever we want to test authorized requests in our tests, we can use this authorized_client fixture instead of the generic client fixture.

Open up the tests/test_users.py file and add a couple tests to the TestAuthToken class.

tests/test_users.py

We’re first testing to see that our auth service is capable of extracting a username from a valid JWT token. Next, we’re ensuring that if the token or secret is wrong in any way, we raise an HTTPException.

Run those tests and make sure they fail. We haven’t implemented the get_username_from_token method so let’s do that now.

services/authentication.py

And we run our tests again and this time they should all pass. Good.

All we’re doing here is decoding whatever token is passed in and raising an HTTPException if there is a problem decoding the token.

Let’s test the one protected route we’re about to make. This route will take advantage of our get_username_from_token method in auth dependencies we’ll define in a minute.

First things, create a new test class in test_users.py.

tests/test_users.py

Run those tests and watch them fail. We’ll fix the starlette.routing.NoMatchFound error by creating our /me/ route.

Open up the api/routes/users.py file and add the following:

api/routes/users.py

Run the tests and watch them fail.

This is where things get interesting. We’re going to need to somehow inject the currently authenticated user into the /me/ route. Remember that we only know the user is logged in by the token passed to our routes in the Authentication header. Instead of parsing the request ourselves and searching for that token, we’re going to hand that responsibility over to FastAPI.

We’ll create an auth dependency that grabs the currently authenticated user from our database and injects that user into our route.

Go ahead and create a new file in the dependencies directory like so:

And add the following code:

api/dependencies/auth.py

So what’s going on here? Let’s first discuss the oauth2_scheme dependency we’ve created.

OAuth2PasswordBearer is a class we import from FastAPI that we can instantiate by passing it the path that our users will send their email and password to so that they can authenticate. This class simply informs FastAPI that the URL provided is the one used to get a token. That information is used in OpenAPI and in FastAPI’s interactive docs.

For us that path is located at /api/users/login/token/, and is our login route. That route looks like this:

The oauth2_scheme variable is an instance of OAuth2PasswordBearer and is also a dependency, so we can use it with Depends. We do just that in our get_user_from_token function. By injecting the oauth2_scheme as a dependency, FastAPI will inspect the request for an Authorization header, check if the value is Bearer plus some token, and return the token as a str. If it doesn’t see an Authorization header, or the value doesn’t have a Bearer token, it will respond with an HTTP_401_UNAUTHORIZED status code for us.

Once that token has been injected into the get_user_from_token function, we use our auth_service to decode the token and search in our database for a user that matches the username in the token payload. If anything goes wrong, we raise an exception. Otherwise we return the user.

That get_user_from_token function is used a sub-dependency in our get_current_active_user function. This dependency takes in the user returned by our get_user_from_token function and ensures that they exist and are active. Otherwise it raises the appropriate exception.

If all goes well, we get our logged in user and can integrate it into any route we want protected.

Head back to the api/routes/users.py file and update it like so.

api/routes/users.py

Run the tests one more time and watch them all pass.

The Interactive Docs

We’ve now done most of the work to implement a proper auth flow in our application and it’d be nice to test it out. Fortunately, FastAPI’s interactive docs make that easy. Head to localhost:8000/docs and there should be some new additions.

open api docs for the me route

That authorize button in the top right lets us enter in an email and password for any user already in our database, and it will authenticate for us at the route we provided in our oauth2_scheme.

See that lock icon next to our new /api/users/me/ route? FastAPI knows that it’s a protected route and requires a token in the Authorization header. If we attempt to execute a request to that endpoint, we see a 401 response containing json { "detail": "Not authenticated" }.

If we then click on the authorize button and enter proper credentials, we should be able to execute that route and be provided with a UserPublic model corresponding to the authenticated user.

Amazing!

Wrapping Up and Resources

This app is finally beginning to come together. By fleshing out a complete registration and login system, we can now start to give users the ability to create cleanings and attach those resources to users in our database. In the next post, we’ll do just that. By the end, users should be able to post cleaning opportunities and other users should be able to offer their services for a given opportunity.

Github Repo

All code up to this point can be found here:

Tags:

Previous Post undraw svg category

User Auth in FastAPI with JWT Tokens

Next Post undraw svg category

Setting Up User Profiles in FastAPI