A Better Authentication API
By: Mike Wolfe
Tags: Python, Fastapi, Json, Json Web Token, Software Development

A Better Authentication API

Using FastAPI to generate JSON Web Tokens

Photo by Philipp Katzenberger on Unsplash

Recently, I wrote a series of posts explaining how JSON Web Tokens could be utilized in an API that was written using Flask. However, a few weeks ago, I discovered how awesome FastAPI is and have been wondering if it’s JWT validation techniques would be a better fit for what I need.

Planning

Before we get started writing any code, it’s always a good idea to do a little planning first. Thinking about the design of the API, we are going to need at least two endpoints.

POST — /authenticationapi/v2/create

GET — /authenticationapi/v2/login

The “create” endpoint adds a new user to the system while the “login” endpoint generates a token for the user. The really cool part about FastAPI is that it has support for pydantic models. Essentially, these models define what the data being passed to the endpoint should look like. They also validate and return an appropriate error for invalid data. The model for the “create” endpoint will look something like this:

class NewUser(BaseModel):

username: str

email: str

password: str

The model for the “login” endpoint will look like this:

class User(BaseModel):

username: str

password: str

Coding Time!

With a plan in place, it’s time to start a little coding. I went ahead and initialized a new instance of FastAPI and added some metadata to it so that Swagger documentation will look a little nicer. After that, I defined the API endpoints and their functions.

With our template set up, we can now dive into finishing the “add” endpoint. As I mentioned earlier, this endpoint adds a new user to the system, but before we can do that, we need to hash the password. Lucky for us, there is a really awesome package called “passlib” that makes hashing very easy. To install it, use the following command:

pip3 install passlib[bcrypt]

Once installed, it can be imported using:

from passlib.context import CryptContext

To hash the password, we first need to create a context. Essentially, this context is a helper that defines that a hashing algorithm and hashes passwords. In our case, the context looks like this:

passwordContext = CryptContext(schemes = ["bcrypt"], deprecated = "auto")

Using the “hash” function, our password will be hashed using the BCrypt scheme. A salt value is built into the function, so if we tried to hash the same value multiple times, we would get a different value. The final step to completing this endpoint is using an asynchronous function to insert the new user into the database. I highly recommend using SQLAlchemy to handle insertion into your database.

The “login” endpoint is a little more complex. When first called, we will use an asynchronous function to get the user’s hashed password from the database. After that, using the “verify” function from the passlib context we can compare the stored hashed password to the password the user provided in the API call. If the user provided the correct password, then a JWT will be created (token expiration time is one hour).

The final result should look a little something like this:

Testing

With our endpoints written, we can finally do some testing. After starting up Postman, I entered the URL for the “add” endpoint.

http://<HOSTNAME>/authenticationapi/v2/add

After that, I added the JSON object that will get sent with our call:

{

"username": "mike",

"email": "mike@testmail.com",

"password": "TESTPASSWORD!!" }

Below are the results of the POST request.

Successfully added user.

Now that our new user was successfully added, let’s try logging them into the system with our “login” endpoint.

http://<HOSTNAME>/authenticationapi/v2/login

The JSON object being sent with the request is:

{

"username": "mike",

"password": "TESTPASSWORD!!" }

Sending the request gets us the following result:

Obtained token successfully.

Fortunately, we were able to successfully obtain a token. However, in order to truly test the endpoint, we need to see what happens when we use an incorrect password. The following shows us the result of that attempt.

Incorrect password test.

The last test that needs to be made is to see if the built-in Swagger documentation works. By this, I mean that would be seeing our two endpoints as well as the objects that get passed to them. Navigating to this URL will verify its functionality.

http://<HOSTNAME>/docs

Verifying working Swagger documentation.

Final Thoughts

So far, we have a pretty good start on an authentication API. We have two simple endpoints that adds a new user to your database and generates a JSON Web Token for an authenticated user. Unfortunately, this API is far from complete, because there is still a lot of work to be done. Specifically, there needs to be more error handling, logging, and a better secret when encoding the JWT just to name a few. Feel free to leave a comment about your thoughts on this API (or any authentication API for that matter). Until next time, cheers!