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)