My project has Token as the default authentication, and my view permissions classes are: AllowAny and a custom permission that for the sake of debugging is always returning False. I'm sending a GET request with no custom headers and receiving 401, with the message:
{
"detail": "Authentication credentials were not provided."
}
Since my view has the AllowAny permission, I suspect it should never be returning 401, and in the specified situation it should be returning 403 instead.
Not 100% sure if this is a bug since it's not common to have AllowAny permission being used alongside other permissions.
See docs here... http://www.django-rest-framework.org/api-guide/authentication/#unauthorized-and-forbidden-responses
Hrm… I think there’s still room for improvement here.
First, I don’t think the text in the docs is clear enough. I see the bolded section:
The first authentication class set on the view is used when determining the type of response.
Something along the lines of the following might be clearer:
The first authentication class set on the view is used when determining the type of response, and any unauthenticated request that fails a permission will return an HTTP 401 Unauthorized status code (even if the particular view does not require authentication).
Second, I don’t think the current functionality is completely right. It seems @filipeximenes (and more recently I) expected that any a view that doesn’t require authentication would _not_ return a 401 when some other permission fails.
It looks like this functionality maybe came in from https://github.com/tomchristie/django-rest-framework/pull/416 and the relevant code block in views.py is:
def permission_denied(self, request):
"""
If request is not permitted, determine what kind of exception to raise.
"""
if not self.request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied()
and within views.py, that method is called from the following permissions checks:
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(request)
def check_object_permissions(self, request, obj):
"""
Check if the request should be permitted for a given object.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_object_permission(request, self, obj):
self.permission_denied(request)
Doesn’t look like an easy change given how the code is structured.
(Edit) Solution - upon thinking about it a bit more, the best solution for a developer using the existing framework seems to be to raise an explicit exception in the permission itself, e.g.,
from rest_framework import permissions, exceptions
class MyPermission(permissions.BasePermission):
def has_permission(self, request, view):
is_ok = some_permission_check()
if not is_ok:
raise exceptions.PermissionDenied()
return is_ok
This is a little annoying, but gets the job done for people who really want it to work this way. Also, you can pass in an option argument to the PermissionDenied exception to specify the detail text that gets returned in the response.
Btw, complaining aside, fantastic work on this framework! :clap:
I had the same problem. I fixed this 'bug' with added authentication_classes = [] to my view.
class MyView(generics.GenericAPIView):
authentication_classes = []
(...)
This is a bug!.
My default authorisation is Token Authorization. The view which I am using does not need any authorization.
Case 1 : No Authorization
I get expected output
Case 2 : Token Auth but wrong token
I get Error 401
Case 3 : Token Auth with correct token
I get expected output
I can confirm and recreate what @dheerajmpai stated above.
This is not a bug. It's a logical error. If my user has a token that is being added to the headers for all requests, but that token happens to be expired, they should still be able to access public endpoints. Please fix.
If there's a bug in DRF, a good place to start would be a PR with a failing test case that demonstrates the issue.
Most helpful comment
I had the same problem. I fixed this 'bug' with added authentication_classes = [] to my view.