FastAPI decorators on endpoint

Question:

I’m trying to create role-based access control on endpoint and since fastAPI has this build-in Depends method with possibility to cache result I’m trying to create something like this

@router.get('/')
#decorator 
@roles_decorator("admin")
async def get_items(user_id: str = Depends(get_current_user)):

return await get_all_items()

get_current_user method will need to receive roles from decorator (in this case admin) and from authorization service receive user_id if role matches provided role. So my question is, can we pass the role from decorator to method in Depends, or is there any chance that we can do this role-based access control with predefined roles for every endpoint?

def get_current_user(role):
    #connect to auth_serivce and do other logic
    return user_id
Asked By: cole

||

Answers:

Instead of trying to mix dependencies and decorators (which won’t do anything good), you can instead use a dynamically configured dependency:

async def get_current_user_with_role(role):
    async def get_user_and_validate(user=Depends(get_current_user)):
        if not user.has_role(role):
            raise 403

        return user.id

    return get_user_and_validate

This will bind the role value you give to the dependency when it gets created:

@router.get('/')
async def get_items(user_id: str = Depends(get_current_user_with_role("admin"))):
    pass
Answered By: MatsLindh

I’m new to Python and FastAPI but passing params to a dependency wasn’t working too well for me, and it started causing the params to appear in Swagger which wasn’t what I wanted.

My endpoints require API keys for authentication, so I check who the API key belongs to and if they have the required permissions to access the endpoint.

Coming from other frameworks where I’d use a decorator for this kind of thing, I ended up implementing it this way in my FastAPI project:

Here’s the decorator:

def has_permission(permission: str):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            api_key = kwargs.get("api_key_header")
            user_service = kwargs.get("user_service")

            user = user_service.get_by_api_key(api_key)

            if permission not in user.permissions
                raise HTTPException(status_code=403, detail="User doesn't have required permissions")

            return await func(*args, **kwargs)

        return wrapper

    return decorator

And this is how I use it on my route:

@router.get("/", summary="Retrieve all users.", response_model=List[UserResponse])
@has_permission("users.get")
async def get_all_users(
    api_key_header: str = Security(api_key_header), user_service: UserService = Depends(UserService)
):
    """
    Retrieve all users.
    """
    return user_service.get_all_users()
Answered By: Dan Moulton
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.