I'm using a custom authentication scheme and I cannot figure out how to get it to send 401 HTTP responses instead of 403. The guide at http://www.django-rest-framework.org/api-guide/authentication/#custom-authentication says to override the authenticate_header method, but this doesn't seem to do anything. The part that is sending the 403 is where the AuthenticationFailed exception is being raised. I assigned the OwnerCredentialsFound to a ModelViewSet via the permission_classes property.
from rest_framework import exceptions
from rest_framework import permissions
from django.contrib.auth.models import User
def authenticateUser(username, password):
try:
user = User.objects.get(username=username)
return user.check_password(password)
except:
return False
class OwnerCredentialsFound(permissions.IsAuthenticated):
def has_permission(self, request, view):
#Check credentials
#If the fields don't exist, the given default value is used
username = request.POST.get('username', None)
password = request.POST.get('password', None)
authenticated = authenticateUser(username, password)
if(not authenticated and username is not None and password is not None):
raise exceptions.AuthenticationFailed('Username/password pair not found')
elif(not authenticated):
authenticated = permissions.IsAuthenticated.has_permission(self, request, view)
else:
#set the user
view.request.user = User.objects.get(username=username)
return authenticated
def authenticate_header(self, request):
return '{"username" : <username>, "password" : <password>}'
UPDATE: It seems that I've confused the authentication and permission classes. I'm using a permission class, but it is the authentication class that has a method called authenticate_header.
Basically, I didn't really understand the difference between permissions and authentications, so that led to confusion. The permissions class has no authenticate_header method, but the authentication class does. Here is what I did to solve the problem:
from rest_framework import exceptions
from rest_framework import authentication
from django.contrib.auth.models import User
def authenticateUser(username, password):
try:
user = User.objects.get(username=username)
return user.check_password(password)
except:
return False
class CustomAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.POST.get('username', None)
password = request.POST.get('password', None)
authenticated = authenticateUser(username, password)
if(not authenticated and username is not None and password is not None):
#authentication attempted and failed
raise exceptions.AuthenticationFailed('Username/password pair not found')
elif(not authenticated):
#authentication not attempted (try other authentications)
return None
else:
#authentication attempted and suceeded
return (User.objects.get(username=username), None)
def authenticate_header(self, request):
return '{"username" : <username>, "password" : <password>}'
In my view:
permission_classes = (IsAuthenticated,)
authentication_classes = (CustomAuthentication, SessionAuthentication)
This confusion of permissions and authentication also explains why my attempt to combine multiple permission classes failed (you may note in my original code that I inherited from a permission class and called its has_permission method in order to get around this). I no longer need a custom permission class because I can just use two authentication classes.
When You return False
from has_permission
, under the hood DRF raises PermissionDenied
exception. Then the exception is caught and processed in a fuction named exception_hanlder
like this:
elif isinstance(exc, PermissionDenied):
msg = _('Permission denied.')
data = {'detail': six.text_type(msg)}
set_rollback()
return Response(data, status=status.HTTP_403_FORBIDDEN)
It looks like You can define some custom Exception, throw it on error in OwnerCredentialsFound.has_permission
and then use a custom exception handler to catch it Yourself. Please read more here:
http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
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