Ideally using django-rest-framework-simplejwt and the authentication class JWTAuthentication, the API should give 403 when I pass the token incorrectly.
Instead, when I am making my API request it is executing successfully even without the Authentication token.
This is a dummy API, my concern is the Authentication should work.
My code looks like this:
class ViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = SomeSerializer
http_method_names = ("post", "patch")
authentication_classes = (JWTAuthentication,)
When I debug I see that it is executing JWTAuthentication, which in turn returns None.
Which is expected since I am not passing the Token in the header.
def authenticate(self, request):
header = self.get_header(request)
if header is None:
return None
Now I think the View should give Permission Denied, which is not happening.
Not able to understand what is missing here.
If you pass incorrect token, it'll return 401 status response.
But if you don't put authorization header on your request, django will not return 401 response and behave with request as AnonymousUser request.
If you want only authenticated users have access to your ViewSet, you should put permission_classes = [IsAuthenticated,] in your ViewSet.
IsAuthenticated permission class can be imported from rest_framework.permissions
Related
I have a DRF view as such:
class DocumentsView(generics.ListCreateAPIView):
permission_classes = ()
authentication_classes = ()
# I typically enforce authentication with
# authentication_classes = (JWTCookieAuthentication,)
def list(self, request):
pagination_class = None
if request.user.is_authenticated:
... return protected data ...
else:
... return generic data ...
I want to allow both users sending a valid token and those not sending a valid token to both get a response from this endpoint. However, request.user.is_authenticated returns False, even when a valid token is sent (I understand why). How can I try to authenticate the user, but still allow them to proceed even if not presenting a token?
Or is better practice to not have the same view to authenticated an unauthenticated users?
What you want is to have no permissions, but still perform authentication.
Authentication validates tokens, gets the user from the database, etc. If you disable it, then no checks of tokens/etc will be performed. This means request.user will always equal AnonymousUser.
class MyView(ListCreateApiView):
permissions_classes = [AllowAny]
It's worth considering using AllowAny as your permission. It is the same as using a blank list [], but makes it clear your intention is to make the view public, rather than a mistake.
I am just a newbie with Django, python. I try to build 1 simple API Collection include CRUD basic, and authentication, authorization.
I have 1 simple Views like:
#api_view(['GET'])
#permission_classes([IsUser])
def get_test(request : Request):
return JsonResponse({"message": "success"}, status = status.HTTP_200_OK)
and IsUser is:
class IsUser(IsAuthenticated):
def has_permission(self, request : Request, view):
token = request.query_params.get('token')
if token:
role = jwt.decode(token.split(" ").__getitem__(1), key="secret_key",algorithms="HS256").get('role')
if role == 'User':
return True
else:
return False
return False
My purpose wants to parse the JWT token and authorization based on that. I wonder don't know if my way is really correct? Can anyone give me some comments as well as documentation to better understand this issue? I don't use any other lib because I want to code by myself at first to understand the flow.
Thanks for helping me.
If you are using the default JWT authentication of DRF then permission class IsAuthenticated will verify the the token for you and instead of specifying token in query parameter specify in headers.
However, if you want to allow a specific type(role) of user to access your endpoint. Then create a subclass of BasePermission class as show in the example.
Then simply add IsAuthenticated in #permission_classes([IsUser & IsAuthenticated]) to make it work.
Again, this would work only if you are using the default Group model.
Here is how you can import it from django.contrib.auth.models import Group
from rest_framework.permissions import BasePermission
USER = 'USER'
class IsUser(BasePermission):
def has_permission(self, request, view, user=None):
if not user:
user = request.user
return USER in [role.name for role in user.groups.filter()]
I have a view with the following permission
permission_classes = [IsOwnerOrReadOnly]
the permission definition is
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
# the object has a owner attribute
return obj.owner == request.user
It listing of the objects works as expected if the user authenticated and pass correct access token. it also works if I do not add Authorization in header.
The only problem happens when the authorization token is expired or wrong. It sends 401 response.
Invalid token header. No credentials provided.
How can I ignore authorization token in django code for right or wrong authorization header.
I found a way to achieve what I intend. However, I am not sure whether this is the standard or not. override perform_authentication in Modelviewset does the work and we can strip the authorization from the header.
def perform_authentication(self, request):
if 'HTTP_AUTHORIZATION' in request.META:
request.META.pop('HTTP_AUTHORIZATION')
Context
My API accepts a jwt-token that I can pass to an external endpoint which will return 401 if invalid/expired or user information if still valid. Based on this I will either return 401 or filtered data belonging to the user. Also, POST request's need to involve writing who this resource belongs to for the GET to work. I tried to do this by overriding the get_queryset and perform_create methods.
My viewset looks something like this:
class ReportViewSet(AuthorizedUserBasedFilteredViewset):
queryset = Report.objects.all()
serializer_class = ReportSerializer
def perform_create(self, serializer):
try:
username = self.get_authorized_user()['user']['username']
except Exception as e:
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
serializer.save(creatd_by=username)
def get_queryset(self):
try:
username = self.get_authorized_user()['user']['username']
except Exception as e:
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
return Report.objects.filter(created_by=username)
This doesn't work because get_queryset expects a queryset as response.
Questions
How do I bubble up the authorize exception in get_queryset? Is there some other method I should be overriding to the authentication entirely? I still need to pass the username recieved to get_queryset for the authentication to be successful
Is there a better way to do this? I feel that the call to the external authentication API and setting the user to be accessible by the get_queryset and perform_create methods would ideally go somewhere else in the code. I looked at the RemoteUserAuthenticationBackend but that still involved creation of a User object but for this use case the user model is entirely external
Instead of
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
use this
raise NotAuthenticated(detail='Token does not exist')
Hope above line has addressed your 1st question.
For 2nd question.
You can extend TokenAuthentication and then implement def authenticate_credentials(self, key): method. It is not a good idea to call external API to fetch user each time. Instead, you should get JTW token one time from external source and then pass JWT token in header like Authorization : Bearer cn389ncoiwuencr for each API call. Then you should decode JWT token in current system.
from rest_framework.authentication import TokenAuthentication
from django.contrib.auth import get_user_model
class CustomTokenAuthentication(TokenAuthentication):
keyword = 'Bearer' # token type
def authenticate_credentials(self, key):
#decode JWT.
username = get_username()
User = get_user_model()
user = User(username=username)
return user, key
Finally, add this class to settings.py file.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'path.of.CustomTokenAuthentication',
)
}
That will apply to all of your views. or you can use view specific auth class.
class Sample(ViewSet):
authentication_classes = (CustomTokenAuthentication,)
From now you can access user by request.user at views or viewset. That's it.
My API is currently protected by the OAuth2TokenAuthentication from django-oauth-toolkit, so that it can validate API requests that contains access token in following ways:
as query param?access_token=xxxx
in header Authorization: Bearer xxxx
while I can hardcode in my API view to try to get the access token from those 2 places, is there a canonical way to obtain the token?
I dug through the code inside OAuth2TokenAuthentication, and borrowed it into my API View:
class IntrospectView(APIView):
"""
An API view that introspect a given token
"""
serializer_class = TokenIntrospectSerializer
authentication_classes = []
permission_classes = []
def get(self, request, *args, **kwargs):
oauthlib_core = get_oauthlib_core()
valid, r = oauthlib_core.verify_request(request, scopes=[])
if not valid:
raise APIException('Invalid token')
return Response(TokenIntrospectSerializer(r.access_token).data)