In today's world, building secure and reliable web applications is more important than ever. One crucial aspect of this is implementing proper authorization protocols to protect sensitive user data and ensure that only authorized users can access certain resources.
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.6+ based on standard Python type hints. One of the key advantages of FastAPI is its built-in support for handling user authentication and authorization.
FastAPI has built-in support for handling authentication through the use of JSON Web Tokens (JWT). Once a user is authenticated, their JWT can be used to authorize them to access certain resources or perform certain actions. In this guide, we'll see how to do just that. Below is an outline on what we'll touch on in this guide.
- FastAPI authentication scheme setup
- Data access protection configuration
- Access authorization
This guide will build on JWT authentication in FastAPI. Yet to see the guide? then check it out, i'll wait here. Github repository is also available, so you can clone the project and begin from where we left off
JWT Authentication in FastAPI ( Comprehensive Guide )
Osazuwa J. Agbonze ・ Jan 14 '23
FastAPI authentication scheme setup
To access core features in most applications, logging in is usually required. While some application would only provide an option to log in with email and password (this is what we'll use in this guide), others might require that and also include log in with google, facebook you get the gist. These options have a distinct interface which collects user credentials and a process (view, controller, service e.t.c) that validates the credentials. All of these together is an authentication scheme: Authentication scheme defines what's needed for an authentication process, these include:
- the kind of authentication to be supported e.g auth with email & password, social auth e.t.c
- an interface to collect auth credentials e.g SPA app / postman e.t.c
- a process to validate the credentials and return a token
Authentication Scheme with different login options
When it comes to setting up authentication scheme, FastAPI shines with it's setup simplicity.Open main.py
and include the following content
# other imports statement at the top
from fastapi.security import OAuth2PasswordBearer
# setup authentication scheme
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
The above code is an authentication scheme which would request that users provide a username and password to login. We imported and instantiated 0Auth2PasswordBearer
with a route to handle login process fed to tokenUrl
param. The login route would generate a JWT token on successful authentication.
Data access protection configuration
Data privacy is an important topic which should be taken very seriously. You only want to grant data access to authorized users. We'll see how to facilitate data privacy in FastAPI by allowing only authorized access to user's profile. Open up main.py
and include the following code
@app.get("/profile/{id}", response_model=UserSchema)
def profile(id:int, session:Session=Depends(get_db)):
"""Processes request to retrieve user
profile by id
"""
return user_db_services.get_user_by_id(session=session, id=id)
We've defined a profile
view which is accessible on route /profile/{id}
. It takes as a path parameter the user's id whose profile is to be viewed. Response type is a UserSchema
. The body of the view feeds the id and a database session to get_user_by_id
service. This service is currently unavailable, open up services/db/users.py
to include it:
# other functions and import statements above
def get_user_by_id(session:Session, id:int):
return session.query(User).filter(User.id == id).one()
get_user_by_id
function queries the User
database table and filter user having an id value similar to that retrieved from path parameter from our profile
view.
At this point we've not set any data restrictions (we'll get to this shortly and you'd see how easy FastAPI has made the setup) meaning all user's profile data is accessible to the general public. Lets test this out. Go to project root and run docker-compose up --build -d --no-deps
Note
You might want to run migrations to avoid database interaction errors. Alembic is smart enough and wouldn't make any migration that has already been made, so it's fine if you re-run the migration.
In your browser, open localhost:8000/docs
and you should be presented with this view
Create a new user by interacting with the signup
route by using the try it out
button. When a user is created, the response would return the new user's id
which will be needed to interact with the profile view. Below is an image of what the response should look like
In my case, the new user id
is 2. we'll proceed to interacting with the profile endpoint with the new user's id. Click on try it out
on the profile endpoint and you should have a form field where an id is requested for.
On clicking execute, you should have a response similar to the one gotten on creation of the user.
To set data privacy, we'll utilize the authentication scheme setup earlier by supplying it as a parameter to profile
view signature.
main.py -- updating profile view
@app.get("/profile/{id}", response_model=UserSchema)
def profile(
id:int,
token: str = Depends(oauth2_scheme),
session:Session=Depends(get_db)
):
"""Processes request to retrieve the requesting user
profile
"""
return user_db_services.get_user_by_id(session=session, id=id)
With that simple addition, we should now have an Authorize button on the docs page as shown below
Can't find expected update on the Docs ?
If you can't find the changes on the docs page or your changes not working as expected, restarting your app container service might fix the issue
docker-compose restart app
Didn't work ? leave a comment below
Attempt to authorize a user by clicking on any of the authorize buttons would result to 422 Unprocessable Entity Error. Clicking on try it out
on the profile endpoint that previously returned the user detail whose id
was passed should now return Unauthorized as seen in the image below
Fixing Unprocessable Entity Error
Our login view is currently using UserLoginSchema
to capture authentication details. While this is not entirely wrong, the token provided as response from the endpoint cannot be used to interact with endpoints that requires authorization on the Docs. So lets fix this
main.py
from fastapi.security import OAuth2PasswordRequestForm
@app.post('/login', response_model=Dict)
def login(
payload: OAuth2PasswordRequestForm = Depends(),
session: Session = Depends(get_db)
):
"""Processes user's authentication and returns a token
on successful authentication.
request body:
- username: Unique identifier for a user e.g email,
phone number, name
- password:
"""
try:
user:user_model.User = user_db_services.get_user(
session=session, email=payload.username
)
except:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid user credentials"
)
is_validated:bool = user.validate_password(payload.password)
if not is_validated:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid user credentials"
)
return user.generate_token()
We imported OAuth2PasswordRequestForm
and utilized it in login function signature as a type to payload
parameter which is automatically injected by Depends
. Changes was also made on user retrieval from database by using payload.username
(initially was payload.email
). Those are all the needed changes.
I'm building a simple API to test a database. When I use GET
request everything works fine, but if I change to POST
, I get 422 Unprocessable Entity
error.
Here is the FastAPI code:
from fastapi import FastAPI
app = FastAPI()
@app.post("/")
def main(user):
return user
Then, my request…
Access authorization
To gain authorized access first click on the authorize button which should present a form with username
and password
field. Provide the email
of the user created earlier as username and the password
to authenticate. On successful authentication, clicking on the authorize button again should show that authorization access has been granted
With authorized access now granted, interacting with the endpoint to retrieve a user's profile would work fine.
Summary
In this guide we looked at authorization scheme and what it's comprised of. We also touched on data privacy and how easy FastAPI has made it's implementation.
Thanks for taking the time to read through. With the ability to setup a JWT authentication and authorization scheme in FastAPI, you can further extend what you've learnt and include it in your project. I'm currently using this concept to restrict unauthorized interaction with an object storage.
Top comments (2)
Hi Osazuwa, can you suggest a method to retrieve the current user and protect an endpoint withouth the user id which it can retrieved with a get_current_user function??
Thanks for your support.
One way i've achieved this is to use jwt token (this post uses OAuth2PasswordBearer for auth) with fastapi Depends callable which takes a function that can decode a jwt token e.g
auth_account
is a function that can decode a jwt token from which the authenticated user on the request can be gotten.I will publish another post on this explaining each details soon