DEV Community

Mandar Vaze
Mandar Vaze

Posted on • Originally published at learnings.desipenguin.com

How to Implement Role based Access Control With FastAPI

Alt Text

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:

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")

Enter fullscreen mode Exit fullscreen mode

Then in your routes file use it as follows:

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
Enter fullscreen mode Exit fullscreen mode

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

allow_create_resource = RoleChecker(["admin", "manager"])
Enter fullscreen mode Exit fullscreen mode

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

So easy first attempt was on the lines of

if user.role not 'admin':
    raise HTTPException(status_code=403, detail="Operation not permitted")
Enter fullscreen mode Exit fullscreen mode

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

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")
Enter fullscreen mode Exit fullscreen mode

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 section of the documentation, and that was that. 🎉

Thanks

I'm grateful for Marcelo aka Kludex and Danny Rohde on FastAPI gitter for the ideas and help.

Top comments (0)