Python Friday #229: OAuth2 and JWT to Protect a FastAPI Application

The HTTP basic authentication from last week has been working, but it feels a bit messy. When we search for a more current style of authentication, we end up with OAuth2 and JWT. There is an example in the FastAPI documentation that I changed a bit to use a different library for JWT, store the secret key in a .env file and use BCrypt to hash the passwords.

This post is part of my journey to learn Python. You find the code for this post in my PythonFriday repository on GitHub.

 

Install PyJWT

To create our JWT tokens, we need a library that transforms the input data into the right fields of JWT. One option we have in Python is PyJWT that we can install with this command:

 

Why not python-jose?

The official tutorial for FastAPI and OAuth2 uses python-jose for JWT. Unfortunately, as with passlib for BCrypt, the last release was 3 years ago – too long for a security related project.

Another reason against python-jose is that I get this warning with Python 3.12:

======== warnings summary ========
test_jwt.py::test_login_works
test_jwt.py::test_login_mike
****\Python\Python312\Lib\site-packages\jose\jwt.py:311: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC:
datetime.datetime.now(datetime.UTC).
now = timegm(datetime.utcnow().utctimetuple())

— Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

There is a small migration guide to move from python-jose to PyJWT. To switch the tutorial from python-jose to PyJWT I only needed to change the imports and replace JWTError with InvalidTokenError:

 

Create a secret key

We can use this command to create a secret key to sign our JWT tokens:

This key we can put into a .env file next to the key SECRET_KEY_ENV:

SECRET_KEY_ENV=……

That way we can keep the secret out of version control and still access it with ease.

 

From username and password to JWT tokens

The OAuth2 and JWT bearer token example needs a bit more work to access the endpoints. We first need to send our username and password to the /token endpoint to get a token that we then use in the header of our requests to the protected endpoint. We can recreate similar tests to last week, but they need more steps and have a bit too much duplication for me – something we address in a future post.

 

The JWT based authentication

I use the last example in the tutorial as the base for this post. While most of the code is the same, there are a few important differences:

  • The secret key is in a .env file that we load at the beginning of our main.py.
  • Instead of python-jose we use PyJWT for the encoding and decoding of the JWT tokens.
  • Instead of passlib we use bcrypt to hash and verify passwords.
  • Our two users from last week are in the fake data store users instead of the demo user of the tutorial.

The extended code for the API now looks like this:

With that code and an empty __init__.py in the same folder, we can now run the tests and they should all pass. If you run pytest with the -s option, you should find the tokens in the output. You can copy them and head over to jwt.io to decode them and see their content:

The decoded token of our users

 

JWT works with the OpenAPI documentation

We can login with username and password with the “Authorize” button at the top so that the built-in JavaScript client in the OpenAPI documentation can grab our JWT token and send it to the authenticated endpoints. That way the flow for the user is comparable to the one for HTTP basic authentication from last week. The main difference for the user is that the login screen offers more fields:

The login screen is like the one for HTTP basic authentication, but it offers us additional fields for client_id and client_secret.

 

Helpful tutorials

If you want to know a bit more about FastAPI and JWT, I can suggest these three tutorials:

 

Next

The authentication with JWT gives me a much better feeling. On the other hand, implementing that many methods for authentication by oneself is risky. A dangerous error is only a typo away. Before we look at some pre-built solutions, we need to separate the authentication code from the business endpoints. Unfortunately, next week we first need to fix a warning that popped up while preparing the next example.

2 thoughts on “Python Friday #229: OAuth2 and JWT to Protect a FastAPI Application”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.