Search code examples
djangodjango-rest-frameworkdjango-authentication

Return values of custom authentication in Django REST Framework


I'm trying to write my basic custom authentication for Django REST Framework. I have the following auth backend:

class JoeAuth(authentication.BaseAuthentication):
    def authenticate(self, request):
        username = request.META.get('HTTP_X_FORWARDED_USER')
        if not username:
            return

        try:
            user = User.objects.get(krb_name=username, active=True).name
        except User.DoesNotExist:
            raise PermissionDenied('Unauthorized user')

        return (user, None)

Accompanied with a view:

@api_view()
def hello(request):
    return Response(data='hello')

And of course enabled in settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'helloworld.auth.JoeAuth',
    )
}

Now, if a request comes in and does not specify the HTTP_X_FORWARDED_USER header, the authenticate() function returns None. According so DRF docs:

To implement a custom authentication scheme, subclass BaseAuthentication and override the .authenticate(self, request) method. The method should return a two-tuple of (user, auth) if authentication succeeds, or None otherwise.

In some circumstances instead of returning None, you may want to raise an AuthenticationFailed exception from the .authenticate() method.

A None means authentication failed and should ideally return 401 or 403. However, in practice this doesn't seem to be the case. A request without the HTTP_X_FORWARDED_USER is simply allowed and 200 is returned:

$ http http://127.0.0.1:8000/ HTTP_X_FORWARDED_USER:joe
HTTP/1.1 200 OK

"hello"

$ http http://127.0.0.1:8000/
HTTP/1.1 200 OK

"hello"

Am I misunderstanding the documentation in the sense that a None is considered a successful authentication attempt?


Solution

  • The problem is that you are confusing authentication and authorization(permissions in Django). What the authentication does is identifyy the user, however it does not in any way, restrict the user - that is the work of authorization(permission). The permission classes do the work of checking the rights a user has over a particular resource. From what I can see, it seems you have a default global AllowAny permission set, which allows anyone access. You need to set the permission to restrict the endpoint to only authenticated users.

    In your case, you need to add a permission_classes to the view or use global perm issions in DRF settings. You can add permission classes to a function-based API view this way:

    from rest_framework.permissions import IsAuthenticated
    from rest_framework.decorators import permission_classes, api_view
    
    
    @api_view()
    @permission_classes([IsAuthenticated])
    def hello(request):
        return Response(data='hello')