1

I am trying to restrict list view using Django Rest Framework.

If I am sending request with wrong token:

Authorization: Token f1cfedb0895105ee088e8aab5bf0ae7ca9752e79

It returns me HTTP 401 error which is correct

But if i just remove Authorization from HTTP Headers it will query without authentication and return HTTP 200

Am i doing something wrong here:

class OrderViewSet(viewsets.ModelViewSet):
    serializer_class = OrderSerializer
    model = Order


    @permission_classes((IsAuthenticated,))
    @authentication_classes((TokenAuthentication,))
    def list(self, request, *args, **kwargs):
        serializer = OrderSerializer(Order.objects.opened(), many=True)
        return Response(serializer.data)

Also create method should not be restricted to authenticated users.

1 Answer 1

2

I would suggest standardizing your Viewset:

class OrderViewSet(viewsets.ModelViewSet):
    serializer_class = OrderSerializer
    model = Order

    def get_queryset(self):
        """QuerySet for this entire ModelViewSet will only return 
        orders which are opened.

        """
        return Order.objects.opened()

    @permission_classes((IsAuthenticated,))
    @authentication_classes((TokenAuthentication,))
    def list(self, request, *args, **kwargs):
        return super(OrderViewSet, self).list(request, *args, **kwargs)

Upon further investigation, I took a look at the source code for TokenAuthentication, and it appears that if you don't send in an authentication token at all, the authenticate() method returns None if the get_authorization_header() method returns nothing. Thus, if you entirely remove the HTTP_AUTHORIZATION from the header, this is the expected behavior.

I believe the intention here is to not raise an exception so that authentication can move on to the next possible authentication class. If this is not what you want to do, you can override the authenticate() method in your own class inherited from TokenAuthentication. See the code below.

def get_authorization_header(request):
    """
    Return request's 'Authorization:' header, as a bytestring.

    Hide some test client ickyness where the header can be unicode.
    """
    auth = request.META.get('HTTP_AUTHORIZATION', b'')
    if isinstance(auth, type('')):
        # Work around django test client oddness
        auth = auth.encode(HTTP_HEADER_ENCODING)
    return auth

class TokenAuthentication(BaseAuthentication):
    """
    Simple token based authentication.

    Clients should authenticate by passing the token key in the "Authorization"
    HTTP header, prepended with the string "Token ".  For example:

        Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
    """

    model = Token
    """
    A custom token model may be used, but must have the following properties.

    * key -- The string identifying the token
    * user -- The user to which the token belongs
    """
    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'token':
            return None

        if len(auth) == 1:
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = 'Invalid token header. Token string should not contain spaces.'
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(auth[1])

    def authenticate_credentials(self, key):
        try:
            token = self.model.objects.get(key=key)
        except self.model.DoesNotExist:
            raise exceptions.AuthenticationFailed('Invalid token')

        if not token.user.is_active:
            raise exceptions.AuthenticationFailed('User inactive or deleted')

        return (token.user, token)

    def authenticate_header(self, request):
        return 'Token'

Finally, to make your token fail with a 401 instead of a 200, you can do:

class YourCustomTokenAuthentication(TokenAuthentication):
    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'token':
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)

        if len(auth) == 1:
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = 'Invalid token header. Token string should not contain spaces.'
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(auth[1])
Sign up to request clarification or add additional context in comments.

2 Comments

I would like to have create method without restrictions.
I updated my answer above. It is the way that the TokenAuthentication class works.

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.