5

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?

2 Answers 2

6

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'
}
Sign up to request clarification or add additional context in comments.

5 Comments

In that case, the response that the client gets is 500
@Ibolit Have you defined Rest framework exception handler in settings.
I also tried implementing the .handle_exception, but it doesn't get called for an exception in dispatch.
@hspandler No, I will look into it.
Yep, writing a proper authentication class worked a charm! Thank you!
5

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.