Authorization on FastAPI with Casbin
Tags: casbin, fastapi, python, tutorial
Published: 2022-04-23 01:47:38

Nowadays, tons of APIs (both external and internal) are created and used every day. With methods like authentication/firewall restriction, we can identify who can invoke the methods, or restrict from where is trying to access. But, can we identify and authorize given users to invoke some methods rather than others? In the following tutorial we will cover how to authorize different users to execute certain REST API methods in an easy and straightforward way. 

## Situation
In this case, we have an Items REST API implemented on Python 3.10 with [FastAPI](https://fastapi.tiangolo.com/) framework. It allows to list all items and get, create and delete an item. All of these operations must be performed by authenticated users. For sake of simplicity, the following users can be used for authentication:

| User        | Password           | Role  |
| ------------- |:-------------:| -----:|
| alice      | secret2 | Admin |
| johndoe      | secret      |   User |


The application consists of the files: main.py, utils.py and requirements.txt, with the following code. 
**main.py**
{% embed https://gist.github.com/teresafds/f3331c84c029bc6446f32bab35559fdd %}

**utils.py**
{% embed https://gist.github.com/teresafds/da10e1b470e004d63da2c74fea56aef5
%}

**requirements.txt**
{% embed https://gist.github.com/teresafds/5bc43c92d43006fe91e38ee306fab3cc
%}

You can create a conda environment, install required packages and run the api with:

```
conda create -n itemsapi pip
conda activate itemsapi
pip install -r requirements.txt
python3 -m uvicorn main:app --reload 
```
After the API is up and running, let's follow these steps to test it:
1. Open http://127.0.0.0:8000/docs url in browser.
2. Click on "Authorize" and login with username and password (as per Users table shown before).
3. To get all items list, select on /items GET API method. Then, click on "Try out" button and on "Execute" button.
4. To delete the item with id = 1, select on /item DELETE API method, click on "Try out" and execute the method with item_id = 1. The response is 204 and item is deleted successfully.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jax9kur88opeg9uajy9h.gif)

## Goal
Although only registered users (johndoe and alice in this case) can perform items actions, all of them are able to delete items. As per their roles, alice should be able to delete items, but johndoe shouldn't. To achieve this we will implement authorization at REST API method level, in an easy and extensible way with Casbin.

## Implementation
### Overview 
[Casbin](https://casbin.org/) is an open source authorization library with support for many models (like Access Control Lists or ACLs, Role Based Access Control or RBAC, Restful, etc) and with implementations on several programming languages (ie: Python, Go, Java, Rust, Ruby, etc). 
It consists of two configuration files:
- A **model file**: a CONF file (with `.conf` extension) which specifies the authorization model to be applied (in this case we will use Restful one)
- A **policy file**: a CSV file that list API methods permissions for each user. 

### Steps

1) Install casbin python package with pip
`pip install casbin`
2) Define Conf policy
Create new file called **model.conf** with the following content: 
{% embed https://gist.github.com/teresafds/7d197df64cd5a05680cabc202aacf57f %} You can find more details about Casbin model syntax on the [documentation](https://casbin.org/docs/en/syntax-for-models)
3) Define Policy file
Create a new CSV file called **policy.csv** and paste the following: 
{% embed https://gist.github.com/teresafds/6b397b3a29f5c6ce81e0aa28bb641ec5
%} Each row is an allowed permissions with the following values: the second column is the user, the third is the API resource or url, and the last one is a set of allowed methods. In this case, alice will have access to list create and delete items, while johndoe may list and create items but not delete them.

4) Update Python API code to enforce authorization.
In the **main.py** file, with following lines:
```
import casbin
...
async def get_current_user_authorization(req: Request, curr_user: User = Depends(get_current_active_user)):
    e = casbin.Enforcer("model.conf", "policy.csv")
    sub = curr_user.username
    obj = req.url.path
    act = req.method
    if not(e.enforce(sub, obj, act)):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Method not authorized for this user")
    return curr_user
```  
It imports the casbin module and create a new authorization function that read the configuration files with `casbin.Enforcer` and enforce the user has the required permission.  
Then, on the defined API methods, change the old method `get_current_active_user` with the new `get_current_user_authorization`

```
@app.get("/items/{item_id}")
async def read_item(item_id: int, req: Request, curr_user: User = Depends(get_current_user_authorization)):
    return items_dao.get_item(item_id)

@app.post("/items/")
async def create_item(item: Item, req: Request, curr_user: User = Depends(get_current_user_authorization)):
    answer = items_dao.create_item(item)
    if not(answer):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Item with given id already exists")
    else:
        return answer

@app.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_item(item_id: int, req: Request, curr_user: User = Depends(get_current_user_authorization)):
    items_dao.delete_item(item_id)
    return Response(status_code=status.HTTP_204_NO_CONTENT)
```

### Test 

1. Start the updated API 
2. Open http://127.0.0.0:8000/docs url in browser.
3. Click on "Authorize" and login with "johndoe".
3. Try to delete item with id=1. It will be rejected with a 401 Unauthorized error.
4. Logout from that user. Then login with "alice".
5. Try to delete item with id=1. The request works fine, returns 204 and item is deleted.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6o6u97eekkkt21unp31.gif)

### Conclusion 
On this post we saw how to use Casbin to implement authorization on REST APIs. Keep in consideration that this example can be extended combining with other authorization models like RBAC, and only changing the model and policy configuration files.