I am doing a (more-or-less) custom authentication in a django-rest-framework
-based application, where I just need to call another microservice to ask it if a token is valid and what username/userid are associated with it. (I don't have a local table for users).
Having found no out-of-the-box solution, I am overriding the dispatch
method (I am using a APIView
-based view), where I make the request to the remote service and, if the response is not 200, I want to return a 403 error.
Here's my code:
def dispatch(self, request, *args, **kwargs):
try:
user_info = user_from_token_in_request(request)
return super().dispatch(*args, **kwargs)
except:
return Response(
"No Auth Token provided or bad Auth Token"
status.HTTP_403_FORBIDDEN,
)
However, when I pass an invalid token, I get this error: AssertionError: .accepted_renderer not set on Response
, because the response context is not initialised when the dispatch
method is processed.
Is there a, sort of, more proper way of doing it?
APIView allow us to define functions that match standard HTTP methods like GET, POST, PUT, PATCH, etc. Viewsets allow us to define functions that match to common API object actions like : LIST, CREATE, RETRIEVE, UPDATE, etc.
In function-based views, we can pass extra context to serializer with “context” parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with “self. context”. From example, to get “exclude_email_list” we just used code 'exclude_email_list = self.
— Django documentation. REST framework supports HTTP content negotiation by providing a Response class which allows you to return content that can be rendered into multiple content types, depending on the client request. The Response class subclasses Django's SimpleTemplateResponse .
REST framework provides an APIView class, which subclasses Django's View class. APIView classes are different from regular View classes in the following ways: Requests passed to the handler methods will be REST framework's Request instances, not Django's HttpRequest instances.
Instead of returning the Response
try raising one of the exceptions provided by DRF
, and its exception handler would automatically handle the rest.
from rest_framework.exceptions import PermissionDenied
def dispatch(self, request, *args, **kwargs):
try:
# ...
except:
raise PermissionDenied("NO Auth Token provided")
With that said, it is not recommended to write your authentication code in your view itself. You should have done this in a custom authentication/permission class.
from rest_framework.authentication import BaseAuthentication
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
try:
user_info = user_from_token_in_request(request)
except:
raise AuthenticationFailed('No auth token provided')
class MyApiView(APIView):
authentication_classes = [MyAuthentication]
Also make sure, you have defined EXCEPTION_HANDLER
in your rest framework settings.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
To answer the original question, handle_exception
is called inside dispatch
, so if you are overriding dispatch
, it will not be called automatically for you. BUT you can call it directly:
from rest_framework.exceptions import PermissionDenied
class SometimesErrorMixin:
should_error = None
def dispatch(self, request, *args, **kwargs):
if not self.should_error:
return super().dispatch(request, *args, **kwargs)
# Lifted from DRF
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
response = self.handle_exception(PermissionDenied())
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
Put this Mixin left most on a ViewSet to use the behavior. Obviously you can replace should_error
with the appropriate logic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With