In this tutorial, you'll learn how to secure a FastAPI app by enabling authentication using JSON Web Tokens (JWTs). We'll be using PyJWT to sign, encode, and decode JWT tokens.
Contents
To calculate signature the following secret is used. Algorithm and secret were used to generate the given token. JWT Key ID Injector - Simple Python Script To. In their most common format, a 'secret key' is used in the generation and verification of the signature. In this article I'm going to show you a less known mechanism to generate JWTs that have signatures that can be verified without having access to the secret key. Pre-requisite: Basic knowledge about JSON Web Token (JWT) I will be asuming you have the basic knowledge of JWT and how JWT works. If not, then I suggest reading the linked Geeksforgeeks article. Let’s jump right into the setup. Ofcource, you need python3 installed on your system. Now, follow along with me.
Authentication in FastAPI
Authentication is the process of verifying users before granting them access to secured resources. When a user is authenticated, the user is allowed to access secure resources not open to the public.
We'll be looking at authenticating a FastAPI app with Bearer (or Token-based) authentication, which involves generating security tokens called bearer tokens. The bearer tokens in this case will be JWTs.
- Project: django-oauth-toolkit-jwt Author: Humanitec File: utils.py License: MIT License.
- Rather than using the JWT for authorization on REST requests, you'll exchange it for an access token, which you'll then include with a Bearer authorization header (if you're constructing your REST requests manually). I use JWT in Python in one of my projects and have a Gist available showing how to use it with simplesalesforce.
Authentication in FastAPI can also be handled by OAuth.
Initial Setup
Start by creating a new folder to hold your project called 'fastapi-jwt':
Next, create and activate a virtual environment:
Feel free to swap out virtualenv and Pip for Poetry or Pipenv. For more, review Modern Python Environments.
Install FastAPI and Uvicorn:
Next, create the following files and folders:
In the main.py file, define an entry point for running the application:
Here, we instructed the file to run a Uvicorn server on port 8081 and reload on every file change.
Before starting the server via the entry point file, create a base route in app/api.py:
Run the entry point file from your terminal:
Navigate to http://localhost:8081 in your browser. You should see:
What Are We Building?
For the remainder of this tutorial, you'll be building a secured mini-blog CRUD app for creating and reading blog posts. By the end, you will have:
Models
Before we proceed, let's define a pydantic model for the posts.
In model.py, add:
Routes
GET Route
Start by importing the PostSchema
then adding a list of dummy posts and an empty user list variable in app/api.py:
Then, add the route handlers for getting all posts and an individual post by ID:
app/api.py should now look like this:
Manually test the routes at http://localhost:8081/posts and http://localhost:8081/posts/1
POST Route
Just below the GET routes, add the following handler for creating a new post:
With the backend running, test the POST route via the interactive docs at http://localhost:8081/docs.
You can also test with curl:
You should see:
JWT Authentication
In this section, we'll create a JWT token handler and a class to handle bearer tokens.
Before beginning, install PyJWT, for encoding and decoding JWTs. We'll also be using and python-decouple for reading environment variables:
JWT Handler
The JWT handler will be responsible for signing, encoding, decoding, and returning JWT tokens. In the 'auth' folder, create a file called auth_handler.py:
In the code block above, we imported the time
, typing
, jwt
, and decouple
modules. The time
module is responsible for setting an expiry for the tokens. Every JWT has an expiry date and/or time where it becomes invalid. The jwt
module is responsible for encoding and decoding generated token strings. Lastly, the token_response
function is a helper function for returning generated tokens.
JSON Web Tokens are encoded into strings from a dictionary payload.
JWT Secret and Algorithm
Next, create an environment file called .env in the base directory:
The secret in the environment file should be substituted with something stronger and should not be disclosed. For example:
The secret key is used for encoding and decoding JWT strings.
The algorithm value on the other hand is the type of algorithm used in the encoding process.
Back in auth_handler.py, add the function for signing the JWT string:
In the signJWT
function, we defined the payload, a dictionary containing the user_id
passed into the function, and an expiry time of ten minutes from when it is generated. Next, we created a token string comprising of the payload, the secret, and the algorithm type and then returned it.
Next, add the decodeJWT
function:
The decodeJWT
function takes the token and decodes it with the aid of the jwt
module and then stores it in a decoded_token
variable. Next, we returned decoded_token
if the expiry time is valid, otherwise, we returned None
.
A JWT is not encrypted. It's based64 encoded and signed. So anyone can decode the token and use its data. But only the server can verify it's authenticity using the JWT_SECRET
.
User Registration and Login
Moving along, let's wire up the routes, schemas, and helpers for handling user registration and login.
In model.py, add the user schema:
Next, update the imports in app/api.py:
Add the the user registration route:
Since we're using an email validator, EmailStr
, install email-validator:
Run the server:
Test it via the interactive documentation at http://localhost:8081/docs.
In a production environment, make sure to hash your password using bcrypt or passlib before saving the user to the database.
Next, define a helper function to check if a user exists:
The above function checks to see if a user exists before creating a JWT with a user's email.
Next, define the login route:
Test the login route by first creating a user and then logging in:
Since users are stored in memory, you'll have to create a new user each time the application reloads to test out logging in.
Securing Routes
With the authentication in place, let's secure the create route.
JWT Bearer
Generate Jwt Secret Key Python Code
Now we need to verify the protected route, by checking whether the request is authorized or not. This is done by scanning the request for the JWT in the Authorization
header. FastAPI provides the basic validation via the HTTPBearer
class. We can use this class to extract and parse the token. Then, we'll verify it using the decodeJWT
function defined in app/auth/auth_handler.py.
Create a new file in the 'auth' folder called auth_bearer.py:
So, the JWTBearer
class is a subclass of FastAPI's HTTPBearer class that will be used to persist authentication on our routes.
Init
In the __init__
method, we enabled automatic error reporting by setting the boolean auto_error to True
.
Call
In the __call__
method, we defined a variable called credentials
of type HTTPAuthorizationCredentials, which is created when the JWTBearer
class is invoked. We then proceeded to check if the credentials passed in during the course of invoking the class are valid:
- If the credential scheme isn't a bearer scheme, we raised an exception for an invalid token scheme.
- If a bearer token was passed, we verified that the JWT is valid.
- If no credentials were received, we raised an invalid authorization error.
Verify
The verify_jwt
method verifies whether a token is valid. The method takes a jwtoken
string which it then passes to the decodeJWT
function and returns a boolean value based on the outcome from decodeJWT
.
Dependency Injection
To secure the routes, we'll leverage dependency injection via FastAPI's Depends.
Start by updating the imports by adding the JWTBearer
class as well as Depends
:
In the POST route, add the dependencies
argument to the @app
property like so:
Refresh the interactive docs page:
Test the authentication by trying to visit a protected route without passing in a token:
Create a new user and copy the generated access token:
After copying it, click on the authorize button in the top right corner and paste the token:
You should now be able to use the protected route:
Conclusion
This tutorial covered the process of securing a FastAPI application with JSON Web Tokens. You can find the source code in the fastapi-jwt repository. Thanks for reading.
Looking for some challenges?
- Hash the passwords before saving them using bcrypt or passlib.
- Move the users and posts from temporary storage to a database like MongoDB or Postgres. You can follow the steps in Building a CRUD App with FastAPI and MongoDB to set up a MongoDB database and deploy to Heroku.
- Add refresh tokens to automatically issue new JWTs when they expire. Don't know where to start? Check out this explanation by the author of Flask-JWT.
- Add routes for updating and deleting posts.
2018-02-16T22:25:45Z
When working with web applications, it is often necessary to generate passwords, tokens or API keys, to be assigned to clients to use as authentication. While there are many sophisticated ways to generate these, in many cases it is perfectly adequate to use sufficiently long and random sequences of characters. The problem is that if you are doing this in Python, there is more than one way to generate random strings, and it isn't always clear which way is the best and most secure.
You would think that adding yet one more method to generate random strings would confuse things even more, but unlike all the other options, the new secrets
module introduced in Python 3.6 is actually designed for this specific use case, so from my part it is a welcome addition to the Python standard library. In this short article I'm going to give you an overview of this new module.
Generating Tokens
The secrets
module is part of the Python standard library in Python 3.6 and newer. You can import this module into your application or into a Python shell as follows:
At the core of this module there are three functions that generate random tokens using the best random number generator provided by your system. The first function generates binary sequences of random bytes:
Generate Jwt Token With Private Key Python
Invoking the token_bytes()
function without any arguments returns a token with a default length that is determined to be sufficiently safe and secure. You can also pass the desired length as an argument, as you can see in the second example above.
The token_hex()
function works in a similar way, but returns a string with the bytes rendered in hexadecimal notation instead of a raw binary string:
With this function, each byte in the sequence is rendered as two hexadecimal digits, so in the second example above, where I request a token with 20 characters, the resulting string is going to be 40 characters long.
The third function in this group is token_urlsafe()
, which returns the random string encoded in base64 format:
The base64 encoding is more efficient than hexadecimal. In the example above you can see that when I requested a token of 20 characters, the resulting base64 encoded string is 27 characters long.
How to know when to use each of these functions? For most cases, the token_urlsafe()
function is probably the best option, so start from that one. If you prefer random strings encoded in hexadecimal notation (which will give you only characters in the 0-9
and a-f
ranges) then use token_hex()
. Finally, if you prefer a raw binary string, without any encodings, then use token_bytes()
.
There are many use cases that benefit from have a simple and secure way to generate tokens. Here are a few examples:
- API keys that are given to clients after they authenticate with username and password
- Password reset tokens to be sent to the user by email
- Initial passwords for new accounts (you will likely want users to change their password after the first login)
- IDs for background tasks or other asynchronous operations
- Passwords to assign to other services such as databases, message queues, etc.
- Dynamically created unique URLs
Generating Random Numbers
While the token generation functions I described in the previous section are the most useful, the secrets
module also provides a few functions that deal with random numbers.
The choice()
function returns a randomly selected item from the list provided as an argument:
This function can be combined with a list comprehension to generate random strings that only use a specific set of characters. For example, if you want to generate a random string of 20 characters that only uses the letters abcd
you can do so as follows:
The randbelow()
function generates a random integer number between 0 and the number given as an argument (not including this number):
Finally, the randbits()
function returns an random integer number that has the specified number of bits:
Conclusion
I hope you found this little article useful. I find the token generation functions, and in particular token_urlsafe()
, very convenient and keep discovering new uses for it. Are you using these functions for an original purpose I have not described in this article? Let me know below in the comments!
Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!
9 comments
#1Eddy van den Aker said 2018-04-20T10:12:29Z
#2Miguel Grinberg said 2018-04-22T06:49:12Z
#3Chinmay Prabhudesai said 2019-01-08T00:06:52Z
#4Miguel Grinberg said 2019-01-08T10:32:19Z
#5Abhi said 2019-02-12T18:29:07Z
#6Fergus said 2020-04-12T10:21:43Z
#7Miguel Grinberg said 2020-04-12T10:27:49Z
#8Rafael Ribeiro said 2020-05-11T03:08:36Z
#9Firas Fatnassi said 2020-05-24T10:44:46Z