5

I'm finding hard to figure out how to return 401 when the token has been deleted in database for whatever reason.

Let me explain.

My general settings use SessionAuthentication and TokenAuthentication schemes.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_FILTER_BACKENDS': (
        'rest_framework.filters.DjangoFilterBackend',
    ),
    'DATETIME_FORMAT': '%a, %d %b %Y %H:%M:%S %z',
    'DATETIME_INPUT_FORMATS': ['iso-8601', '%Y-%m-%d %H:%M:%S', '%a, %d %b %Y %H:%M:%S %z'],
    'DATE_FORMAT': '%Y-%m-%d',
    'DATE_INPUT_FORMATS': ['%Y-%m-%d', '%m/%d/%YYYY'],
    'PAGE_SIZE': 20
}

I have a view for generating the Auth Token, like this:

class AcmeObtainAuthToken(APIView):
    throttle_classes = ()
    permission_classes = ()
    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
    renderer_classes = (renderers.JSONRenderer,)
    serializer_class = AcmeAuthTokenSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        serializer.context = {'request': self.request}
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
       return Response({'token': token.key,
                     'school': school,
                     'user': user.id})

obtain_auth_token = AcmeObtainAuthToken.as_view()

My problem is that when token stored in db is gone for whatever reason and clients send the token, I'm getting 403, when I require 401.

Looking at the docs this is really cryptic:

The kind of response that will be used depends on the authentication scheme. Although multiple authentication schemes may be in use, only one scheme may be used to determine the type of response. The first authentication class set on the view is used when determining the type of response.

It says it depends but how? No example is given...kinda confusing on how DRF does its magic under the hood here...

AlejandroVK
  • 7,373
  • 13
  • 54
  • 77
  • delete 'rest_framework.authentication.SessionAuthentication' in DEFAULT_AUTHENTICATION_CLASSES – Ykh Jun 21 '17 at 07:30

2 Answers2

4

403 is the response you should get in that instance. Have a look at this link:

The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated.

In essence, the client made a correct request but the token is missing, therefore his access is forbidden (403).

If you really want to respond with a 401 error, you can try something like the following in your BloowatchObtainAuthToken view:

class BloowatchObtainAuthToken(ObtainAuthToken):
    def post(self, request, *args, **kwargs):
        response = super(BloowatchObtainAuthToken, self).post(request, *args, **kwargs)
        try:
            token = Token.objects.get(user=request.user) # or what you are validating against
            other stuff here...
        except Token.DoesNotExist:
            return Response({'error': 'Token does not exist'}, status=401)
John Moutafis
  • 22,254
  • 11
  • 68
  • 112
  • 1
    Thanks John, yeah I know this is not very conventional, but I really need 401 in this case because how the front-end is dealing with this, we're redirecting to login upon 401, not on 403, and that's a problem, because user will get 403's all over the place but won't even know that has to login again...cheers! – AlejandroVK Jun 21 '17 at 08:02
  • https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses – xpeiro May 27 '20 at 15:45
0

We can overwirte exception_handler of restframework

In your authenticate function:

msg = 'Token not valid'
raise exceptions.AuthenticationFailed(msg)

In your overwrite:

from rest_framework.views import exception_handler
def core_exception_handler(exc, context):
    response = exception_handler(exc, context)
    if response is not None:
        detail = []
        for field, value in response.data.items():
            if isinstance(value, list):
                detail += value
            else:
                detail.append(value)
        response.data['detail'] = ' '.join(detail)
        if response.data['detail'] == 'Token not valid':
            response.status_code = 401



Dharman
  • 30,962
  • 25
  • 85
  • 135
Cuong Ta
  • 1,171
  • 1
  • 10
  • 12