JWT Authentication with FastAPI and AWS Cognito

Medium says I should add a picture, so here we go. Photo by Pietro Jeng on Unsplash

In this article I’ll show the following:

1. How to get the public key for your AWS Cognito user pool.
2. How to verify a JWT in Python.
3. How to integrate the code into FastAPI to secure a route or a specific endpoint.
4. Bonus: How to extract the username, so that the API handler can work with it.

Background

JSON Web Tokens are represented as an encoded string and contain three parts: The header, the payload/claims, and the signature. The header has information about the algorithm used to sign the token, while additional information like the username is stored in the payload. When a JWT is created–in our case by AWS–the issuer uses a secret key to create the signature. To ensure that no-one tampered with the payload, we have to verify that the signature still matches the payload using the public key. If you’re interested in learning more about JWTs, have a look at JWT.io.

Getting the AWS Cognito public keys

Receiving the public keys is fairly easy once one has dug through the sheer endless AWS documentation. They are saved in a JSON file under the URL:

https://cognito-idp.{AWSREGION}.amazonaws.com/{POOLID}/.well-known/jwks.json

Verifying a JWT in Python

We’ll first have to install a new package that deals with all the JWT data: python-jose.

Get the correct public key

Let’s get started by taking our JWT token and find the matching public key based on the key id:

Verify the JWT

Now that we have our public key, it’s time to verify our token.

hmac_key = jwk.construct(get_hmac_key(token, jwks))

message, encoded_signature = token.rsplit(".", 1)

decoded_signature = base64url_decode(encoded_signature.encode())

return hmac_key.verify(message.encode(), decoded_signature)

Protecting FastAPI with JWT

Let’s integrate this into our FastAPI app. We can achieve this by defining a dependency. Before the code of a handler is executed, the dependency function is run. If we find anything problematic in the request, we can raise an exception and send an error response right away.

Authorization: Bearer JWTTOKENeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd…

Bonus: Extracting the username from the JWT

In case of AWS Cognito, the username is saved in the JWT payload. That can be pretty useful, as we now don’t have to transfer the name via a GET or URL parameter, or even in our POST body. As it turns out, a handler in FastAPI can directly receive the result of a dependency, if we define it as a parameter to our function. We can simplify matters even more if we wrap this step into a helper function:

Summary

Building this little extension for FastAPI has shown me how much fun it is to work with this library and the custom dependency seamlessly integrates into the code. I’ve added a repository with my code and a demo project to GitHub:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Johannes Gontrum

Johannes Gontrum

Language technology enthusiast & NLP consultant.