#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.
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |
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:

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:

Helpful tutorials
If you want to know a bit more about FastAPI and JWT, I can suggest these three tutorials:
- Securing FastAPI with JWT Token-based Authentication
- Securing FastAPI with JWT Token-based Authentication
- How to Add JWT Authentication in FastAPI – A Practical Guide
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.