User Authentication¶
Now let's discuss something integral to almost every project: user authentication.
I can't tell you how many hours we've spent thinking about this topic, mulling over what frameworks to use, how to integrate it with our databases, etc. Talk about boring! 😴
Thankfully, FastAPI [] makes it extremely easy, and with Zentra it's straight out of the box! 😍
We abstract a lot of the details away using the zentra_api
package to keep things simple for you, but still give you the freedom to configure authentication how you want!
Our authentication files live in the app/auth
directory, shown below.
Authentication Directory | |
---|---|
1 2 3 4 5 6 7 8 9 |
|
You may have noticed that the folder follows the exact same file structure as the routes we created in the Creating Routes tutorial. We've done this deliberately!
The auth
directory is just another set of API routes but separated for convenience.
Auth Reset
Don't like how we've done things? Just delete the folder and start fresh! Don't worry, we won't be offended! 😉
Warning
Technically, there is a little more you should remove when getting rid of the auth
routes, such as the configuration settings and the database models 😅, but simply removing the folder is good enough to disable the routes.
We plan to add a --no-auth
flag in a future version that will do all of this for you, but 9 times out of 10 you'll need authentication anyway! 😁
The main thing you need to know here is how the routes work, rather than the underlying functionality. Feel free to explore the code yourself! It's an extension of the FastAPI [] security tutorial with refresh tokens and a bit of Zentra flair 😉.
Okay, now let's check out our routes!
Routes¶
Note
All authentication routes start with api/auth/
. This simple naming convention keeps our API consistent and easy to use.
We follow the same pattern with our token routes - api/auth/token
.
Zentra API has five starting authentication routes:
/api/auth/users/me
- retrieves the user's own details, if they are authenticated./api/auth/register
- creates a user in the database given ausername
andpassword
./api/auth/token
- provides an access token for the user, if their login details are correct/api/auth/token/verify/{token}
- verifies that an access token is valid (e.g., hasn't expired yet)/api/auth/token/refresh
- creates a new access token from the refresh token
Get User¶
Route
So we know this route get's the user's details, but what details? Well, this depends on two factors:
- What user information you are storing in the database
- What information the
GetUser
response model has access to
By default, we use two separate database tables for our users:
DBUserDetails
for personal information, andDBUser
for login credentials
We've found this to be effective for both performance and security, especially when storing a lot of personal information.
Here's our tables:
db_models/user.py | |
---|---|
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 |
|
Notice how we only capture three main details here: the user's full_name
, email
, and phone
number. Feel free to update this as needed! 😁
So, what about our GetUser
response model? Using a bit of Pydantic [] ✨ (courtesy of FastAPI) and Python class inheritance, we combine the UserBase
with the UserDetails
model.
auth/schema.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
So our data could look like this:
Example Data JSON | |
---|---|
1 2 3 4 5 6 7 |
|
Great! That's simple enough, but should we really only be sending this information through our route? It's not very informative. Surely, there's a better way?
This is where one of Zentra's unique features come in! When passing our response model through a zentra_api.responses.SuccessResponse
we get a way more detailed and informative JSON response.
For this specific route, here's our responses:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 |
|
Like ✨, we now immediately see if the response is successful, what type of response code is passed, we get the same data we needed, and get to see any HTTP headers that were passed with the request.
Your API's just got a whole lot funner to work with! 😁
Tip
You can learn more about how these responses work in our Route Responses page.
Onto the next one!
Register User¶
Route
Unlike our previous route, that reads information from the database, this one adds a new user to the DBUser
table.
Instead of using the GetUser
response model, it uses the CreateUser
response model.
auth/schema.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
This is pretty self-explanatory. Given three values: username
, password
, and is_active
; we create a new user in the database.
Here's an example of the routes responses:
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 |
|
Login For Access Token¶
Route
Next, we have our login route for retrieving an access and refresh token. Access and refresh tokens are JSON Web Tokens (JWTs) that act as a form of authentication to the API. They both have a slightly different purpose. Here's a brief overview:
- Access token - allows the user to use the API
- Refresh token - acts as a user session for a period of time
In our case, we use the HS256
algorithm for encryption with a 15
minute expiry for access tokens and a 7
day expiry for refresh tokens. They use the AUTH__SECRET_ACCESS_KEY
and AUTH__SECRET_REFRESH_KEY
, respectively, found in your .env
file.
Updating Auth Settings
We want to provide a solution that works out of the box without overwhelming you with configuration settings, so we deliberately fixed the algorithm and expiry times - it's less things to worry about!
However, if you need more flexibility you can tweak these settings using the .env
file. Here's an example:
.env | |
---|---|
1 2 3 4 5 6 7 8 |
|
- The JWT encryption keys. Keep them secret, keep them safe! 🤫
- The encryption algorithm. Currently, this is limited to three options:
['HS256', 'HS384', 'HS512']
- The access token expiration time in minutes
- The refresh token expiration time in minutes.
10080 = 7 days
. This always lasts longer than your access token
JWTs are out of the scope of this tutorial, but if you want to learn more, we highly recommend you check out these links from JWT.io [] and Auth0 [].
Here's an example of the routes responses:
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 |
|
Verify User Token¶
This route compliments the previous one and simply verifies that an access token is valid using your AUTH__SECRET_ACCESS_KEY
in your .env
file.
Here's an example of the routes responses:
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 |
|
Refresh Access Token¶
Route
The last route is another simple one! Given a refresh token it creates a new access token for the user.
When working with frontend applications, you'll often find yourself using this route and the token verification one together. After all, when an access token expires, you'll need to refresh it!
Here's an example of the routes responses:
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 |
|
Future Plans¶
So far our routes focus on JWT tokens and OAuth2 authentication. This is great for most use cases but sometimes you may need something a little more extensive such as Oauth2 scopes, cookies, or API keys.
We have plans to integrate these in the future, but ultimately it's up to you to decide what type of authentication you need. Zentra is just an extension on top of FastAPI, so the possibilities are truly endless. ✨
Okay, now that we understand more about our authentication, let's move onto our project settings. See you there! 😁