Securing a FastAPI route using JWT token (step-by-step)

Nir Alfasi
Israeli Tech Radar
Published in
4 min readMar 20, 2022

--

By the end of this post, we’ll build a small FastAPI server, send it a request and receive a response, and add authentication/authorization around a route!

Why FastAPI?

I chose FastAPI for this demo because of its growing popularity over the past couple of years:

  • It’s the third most loved web framework in StackOverflow 2021 Developer Survey
  • It is used by large companies like Uber and Netflix to develop some of their applications
  • It fully supports async which makes it really fast!
  • It has a declarative routing & validation
  • It has an automatic documentation generation (swagger)

If you’re not convinced by now, I recommend this blog post for a more thorough overview!

Important Note I: the commands in this post were written & executed on MacOS (on zsh), and they will probably work on any Linux distro, but if you’re working on windows you’ll have to find the parallel commands, sorry for that…

Second, This post assumes that you already have python3 and pip installed on your machine, in case this assumption is wrong: you can find a good explanation here on how to do it.

Let’s start

Let’s begin by creating a new project folder:

mkdir server
cd server

Now let’s create and activate a virtual environment, in case venv is not installed we’ll want to first run:

python3 -m pip install --upgrade pip
python3 -m pip install --user virtualenv

Otherwise, move on to:

python3 -m venv ./venv
source ./venv/bin/activate
# whenever you want to exit venv type: "deactivate"

If you’re not familiar with the advantages of using virtualenv with Python I can’t stress enough how important it is to take the time and study it, but for now, I’ll quote the docs:

virtualenv is used to manage Python packages for different projects. Using virtualenv allows you to avoid installing Python packages globally which could break system tools or other projects.

Now let’s install the two dependencies that we need: FastAPI and PyJWT, but before we do that let’s make sure that pip is up-to-date:

python3 -m pip install --upgrade pip
pip3 install "fastapi[all]"
pip3 install PyJWT

Important Note II: the three steps we did so far, are good for any python project we’ll want to create in the future:

  • create a folder and go inside it
  • create venv and activate it
  • install the dependencies

Now we’re ready for the code!

Let’s create a main.py file using our favorite IDE (I’m using VSCode or pycharm when I write python) and paste the following code snippet:

Now we can run the code with:

python3 -m uvicorn main:app --reload

To test our server, we can use the following curl:

curl --location --request POST 'http://127.0.0.1:8000' \
--header 'Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0.bqxXg9VwcbXKoiWtp-osd0WKPX307RjcN7EuXbdq-CE' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Foo",
"gender": "male",
"age": 25.5,
"checked": false
}'

Pay special attention to class Person — this is the way FastAPI wants us to work with Objects: in this example, we’re receiving an object from the client via a POST HTTP request and this person object should align with the expected structure of the class, that is, if we will not send a mandatory attribute we’ll receive a 422 unprocessable entity HTTP error.

The second thing I’d like to delve into is the signature of the route: the decorator @app.post("/")is pretty self-explanatory: it routes the POST requests to/ on our localhost — to this function. The interesting part is the parameters received by the function:

1. person: Person
2. authorization: str = Header(None)

This signature mandates that the body of the request will contain a JSON object with the structure dictated by Person class and that the request will have an Authorization header. We can make a header optional by doing the following:

authorization: Optional[str] = Header(None)

but there’s no point in doing it here since we’ll use this header to verify that the user who sent the request is authorized to perform this action.

Important Note III: this is the place to stress that this simplified example should NOT be used in production “as is”: PyJWT supports different algorithms for signing and we should use a stronger algorithm (e.g. RSA) or at least stronger secrets. The secrets should NEVER be hardcoded but rather saved as environment variables and approached via a config module. If you’re interested to learn more about these options, PyJWT docs have some nice examples!

Before we part ways I would like to encourage you to play and expand this code sample. For instance, we can add a DB connection, add a Users Table and add code to verify that the user who sent the request has permission to create/modify the object. Then we can also add a Persons Table which will save the related data, and also add routes to modify and delete an existing person. For extra credit, you can improve the implementation of JWT Token that was presented here and use “RS256” algorithm with private and public certificates or learn how to work with “bearer token” (a nice example can be found here).

Enjoy your dive-in & Good luck!

--

--

Nir Alfasi
Israeli Tech Radar

“Java is to JavaScript what Car is to Carpet.” - Chris Heilmann