In my app, I want to apply access to a given endpoint based on a role, which is an enum. The way it all works is that a logged in (authorized) user, wants to get access to some resources, or create a new user etc…, then his jwt token is decoded, so we can see his roles (enum). I’m going to create 3 functions (permission_user, permission_admin, permission_manager) that read the roles of the user and based on it, give access or not. I know that I could create 6 functions (permutations), such as permission_user_and_manager, but I want to solve this in a more professional way. I would like to do something based on:
@app.get("/users") #example endpoint
def fetch_users(is_auth: bool = Depends(permission_admin or permission_manager)
.
.
Unfortunately it doesn’t work, do you know any solutions?
Advertisement
Answer
I would supply the value as another dependency which will return a 403 if the enum is not an appropriate value. I would expect a separate dependency that handles the actual auth and returns an enum value for the permissions (e.g. something like AuthRole
).
def admin_permissions(auth_role: AuthRole = Depends(get_auth_role)):
if auth_role!= AuthRole.ADMIN:
raise HTTPException(
status_code=403,
detail="User must be an admin to perform this action"
)
In your definition of the endpoint route, you can specify this method as a depends that must be performed before the call happens. You could also apply this to an ApiRouter
class to avoid duplication.
@app.get("/users", dependencies=[Depends(admin_permissions)])
def fetch_users():
Now you will only enter the body of fetch_users
if the admin_permissions
dependency does not raise the 403 response code.
If you want to parameterize this further, you can use an advanced dependency that uses a class instances __call__
method to perform the work. Then you can provide multiple roles that are acceptable instead of just one. That would look something like this:
class AuthChecker:
def __init__(self, *roles: AuthRole) -> None:
self.roles = roles
def __call__(self, auth_role: AuthRole = Depends(get_auth_role)):
if auth_role not in self.roles:
roles_values = " or ".join([role.value for role in self.roles])
raise HTTPException(
status_code=403,
detail=f"User must be in role {roles_values} to perform this action"
)
@app.get("/users", dependencies=[Depends(AuthChecker(AuthRole.ADMIN, AuthRole.MANAGER))])
def fetch_users():
return "users"
Full example to play with:
from enum import Enum
import uvicorn as uvicorn
from fastapi import FastAPI, Header, Depends, HTTPException
app = FastAPI()
class AuthRole(Enum):
ADMIN = "admin"
MANAGER = "manager"
NORMAL = "normal"
def get_auth_role(auth_role: AuthRole = Header()) -> AuthRole:
return auth_role
class AuthChecker:
def __init__(self, *roles: AuthRole) -> None:
self.roles = roles
def __call__(self, auth_role: AuthRole = Depends(get_auth_role)):
if auth_role not in self.roles:
roles_values = " or ".join([role.value for role in self.roles])
raise HTTPException(
status_code=403,
detail=f"User must be in role {roles_values} to perform this action"
)
@app.get("/admin", dependencies=[Depends(AuthChecker(AuthRole.ADMIN, AuthRole.MANAGER))])
def admin(auth_role: AuthRole = Depends(get_auth_role)):
return auth_role
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)