How to Implement Role based Access Control With FastAPI
Tags: python, fastapi, api, accesscontrol
Published: 2020-12-30 16:19:08

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/5far77562a70u7l3bz7k.jpg)

## What is Role based Access Control (RBAC) ##

Most of the CRUD apps, require some level of role based access control.

You may have at least two types of users.

1. Elevated permission user (admin, root or superuser)
2. Normal user aka everyone else ;)

More likely you have more levels in between.

This means only the users with specific *role* can access certain API endpoints
or operations e.g. Allow everyone the `GET` operation, but only `admin` can `DELETE`.
Some levels in-between can create/update etc.

## Code ##

Following code assumes your `User` model has a `role` attribute.
It is better to have a default value so that every user created starts
with lowest level, even if role is not assigned when creating.

Let us first define the `RoleChecker` class as follows:

``` python
class RoleChecker:
    def __init__(self, allowed_roles: List):
        self.allowed_roles = allowed_roles

    def __call__(self, user: User = Depends(get_current_active_user)):
        if user.role not in self.allowed_roles:
            logger.debug(f"User with role {user.role} not in {self.allowed_roles}")
            raise HTTPException(status_code=403, detail="Operation not permitted")

```

Then in your routes file use it as follows:

``` python
allow_create_resource = RoleChecker(["admin"])

@router.post(
    "/some-resource/",
    response_model=schemas.MyResource,
    status_code=201,
    dependencies=[Depends(allow_create_resource)],
)
def add_resource(resource: schemas.ResourceCreate, db: Session = Depends(get_db)):
    # Some validation like resource does not already exist
    # Create the resource
    pass
```

Sometimes you want to allow multiple roles to perform certain operation.
That is why, `RoleChecker` takes a list of roles like :

``` python
allow_create_resource = RoleChecker(["admin", "manager"])
```

## Learning (Or how I got here) ##

If you came here just looking for solution, you can stop reading now.

Read on, to know how I reached the solution, things I tried (and failed)

(Sometimes such details give you an idea for something you may want in the future)

As you may know, you can get the current user details in the API via
Dependency Injection via `user: User = Depends(get_current_user)`
See the [documentation](https://fastapi.tiangolo.com/tutorial/security/get-current-user/)

So easy first attempt was on the lines of

``` python
if user.role not 'admin':
    raise HTTPException(status_code=403, detail="Operation not permitted")
```

I extended the above to `user.role not in ["admin", "manager"]` to allow
multiple roles to perform that operation.

It works for "proof of concept", but we cant be adding similar code **everywhere**

Then I created

``` python
def verify_role(required_role: List, user: User = Depends(get_current_active_user)):
    if user.role not in required_role:
        raise HTTPException(status_code=403, detail="Operation not permitted")
```

I needed to pass the list of roles to the function. Unfortunately I could not
call this via `Depends`. I kept getting `Depends has no attribute ...` error.

Also, I need to call this from the `router` decorator function as
`dependencies=[Depends(my_func)]` rather than in the function param
like  `user: User = Depends(get_current_user)`

Finally another user pointed me to [this](https://fastapi.tiangolo.com/advanced/advanced-dependencies/#parameterize-the-instance?) section of the documentation, and that was that. 🎉

## Thanks ##

I'm grateful for [Marcelo aka Kludex](http://marcelotryle.com/) and [Danny Rohde](https://github.com/dannyrohde) on FastAPI gitter for the ideas and help.