Implementing Authorization in FastAPI: A Step-by-Step Guide to Securing Your Web Applications
Tags: api, security, tutorial
Published: 2023-02-03 07:09:34

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.

1. FastAPI authentication scheme setup
2. Data access protection configuration
3. Access authorization 

---

<center><b>To follow along, do note:</b></center>
<code> This guide will build on <b>[JWT authentication in FastAPI](embed https://dev.to/spaceofmiah/jwt-authentication-in-fastapi-comprehensive-guide--c0p)</b>. Yet to see the guide? then check it out, i'll wait here. <b>[Github repository](https://github.com/spaceofmiah/learn-fastapi/tree/0eec5cbec9cc678bab6c768a4c9a61f7a9706eda)</b> is also available, so you can clone the project and begin from where we left off </code>


{% embed https://dev.to/spaceofmiah/jwt-authentication-in-fastapi-comprehensive-guide--c0p %}
<div>
<center><small><em>Generating JWT token in FastAPI for authentication</em></small></center>
</div>

---

## 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 to sign in to spaceofmiah](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqg2ppv5yisd8kor05lg.jpg)<center>
<small>Authentication Scheme with different login options </small>
</center>

---

When it comes to setting up authentication scheme, FastAPI shines with it's setup simplicity.Open `main.py` and include the following content

```python
# 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](https://dev.to/spaceofmiah/jwt-authentication-in-fastapi-comprehensive-guide--c0p) 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

```python
@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:

```python
# 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` 

---

<code><b>Note</b></code>
<em><code>You might want to <b><a href="https://dev.to/spaceofmiah/jwt-authentication-in-fastapi-comprehensive-guide--c0p?#running-migrations">run migrations</a></b> 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.</code></em>

---

In your browser, open `localhost:8000/docs` and you should be presented with this view


![Build with FastAPI - docs route snipshot of profile view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcdu6l9ggtucb8hgkz5u.png)<center><small><em>Build with FastAPI - docs route snipshot of profile view</em></small></center>

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


![Build with FastAPI - create new user response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mm9zn9he8tjzw698vvbe.png)<center><small><em>Build with FastAPI - create new user response</em></small></center>

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.


![Build with FastAPI - user profile route with empty id field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y02uq0xthfz7mebj93yr.png)<center><small><em>Build with FastAPI - user profile route</em></small></center>

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`
```python
@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 ?
<em><code>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 <pre>docker-compose restart app</pre> <br/> Didn't work ? leave a comment below </code></em>

---

![Build with FastAPI - authorize button](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k2g6iatbm4hotnx8kqb2.png)<center><small><em>Build with FastAPI - authorize button</em></small></center>

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


![Build with FastAPI - unauthorized access](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/44sfhbyk2bx71k382fpi.png)<center><small><em>Build with FastAPI - unauthorized access</em></small></center>

### 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

<code><em>main.py</em></code>
```python
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. 

{% embed https://stackoverflow.com/questions/59929028/python-fastapi-error-422-with-post-request-when-sending-json-data/75329127#75329127 %}

## 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

![Build with FastAPI - successful authorization](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gxq2ln0qivt3ezxhu244.png)<center><small><em>Build with FastAPI - successful authorization</em></small></center>

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](https://dev.to/spaceofmiah/jwt-authentication-in-fastapi-comprehensive-guide--c0p) 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.

<code>[Code is on Github](https://github.com/spaceofmiah/learn-fastapi)</code>