Reputation: 2821
My application has an AuthenticateService
implemented as follows:
from domain.ports.repositories import ISalesmanRepository
from fastapi import HTTPException, status
from fastapi_jwt_auth import AuthJWT
from fastapi_jwt_auth.exceptions import JWTDecodeError
from shared.exceptions import EntityNotFound
from adapters.api.authentication.config import User
class AuthenticateService:
def __init__(self, user_repo: ISalesmanRepository):
self._repo = user_repo
def __call__(self, auth: AuthJWT) -> User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
auth.jwt_required()
user_id = auth.get_jwt_subject()
except JWTDecodeError:
raise credentials_exception
try:
user = self._repo.get_by_id(user_id)
return user
except EntityNotFound:
raise credentials_exception
So the behavior is basically:
The problem is that in every controller implemented I have to repeat the process. I tried to implement a decorator that injects the user into the controller in case of success but I couldn't. I'm sure that the best way to implement this is to use fastAPI's Depends
dependency injector.
Today, a controller looks something like this:
from typing import Optional
from adapters.api.services import authenticate_service, create_sale_service
from fastapi import APIRouter, Depends
from fastapi_jwt_auth import AuthJWT
from pydantic import BaseModel
router = APIRouter()
class Request(BaseModel):
code: str
value: float
date: str
status: Optional[str] = None
@router.post('/sale')
def create_sale(request: Request, auth: AuthJWT = Depends()):
user = authenticate_service(auth)
result = create_sale_service.handle(
{"salesman": user, "sale": request.dict()}
)
return result.dict()
How can I abstract my authentication so that my controllers look like any of the versions below:
# Option 1: decorator
@router.post('/sale')
@authentication_required
def create_sale(request: Request, user: User): # User is the `__call__` response from `AuthenticateService` class
result = create_sale_service.handle(
{"salesman": user, "sale": request.dict()}
)
return result.dict()
# Option 2:
@router.post('/sale')
def create_sale(request: Request, user: User = Depends(authenticate_service)): # Something like that, using the depends to inject User to me
result = create_sale_service.handle(
{"salesman": user, "sale": request.dict()}
)
return result.dict()
Upvotes: 4
Views: 2721
Reputation: 2821
So I read a little more the Depends
documentation and realize whats was going wrong with my attempt to inject the user on controller signature.
Right way to implement:
> AuthService class
class AuthenticateService:
def __init__(self, user_repo: ISalesmanRepository):
self._repo = user_repo
def __call__(self, auth: AuthJWT = Depends()) -> User:
...
authenticate_service = AuthenticateService(user_repository)
> Controller
@router.post('/sale')
def create_sale(request: Request, user: User = Depends(authenticate_service)):
result = create_sale_service.handle(
{"salesman": user, "sale": request.dict()}
)
return result.dict()
Upvotes: 2