jazzband / djangorestframework-simplejwt

A JSON Web Token authentication plugin for the Django REST Framework.
https://django-rest-framework-simplejwt.readthedocs.io/
MIT License
4k stars 661 forks source link

Mobile browser does not authenticate token while using ModelViewSet. #442

Open Antsthebul opened 3 years ago

Antsthebul commented 3 years ago

So i have the "typical" use case of storing the token in Local Storage. Im using a DjangoRest/React front application setup. Now the view below is simple and returns an object. The problem is when I call from the frontend on a MOBILE browser it never authenticates. I have allowed the token to be visible in the html (since I dont know how else to check for this on mobile) for testing purposes, so I know during both mobile and desktop browsersing tokens are being created, being stored correctly, and redux is pickng up the state (retrieving token). Oddly enough if I change to using, a func based view, and calling jwt.decode() inside of the funciton. The view will properly raise an exception if an incorrect token is passed. Not sure if anyone else is having the same issue or if mine is a duplicate. MAybe the issue is my understanding of model Viewsets? Again, it works in the browser on desktop, I have the settings correct in the settings.py. If i need to post anymore info I will gladly, thanks so much for your help!

desktop only , _even with permissionclasses commented out. Still fails

class TrackViewSet(viewsets.ModelViewSet):
     permission_classes = [
         permissions.IsAuthenticated
     ]
    serializer_class = TrackSerializer

    def get(self):
        print('hi') # does not make it here on mobile
        genre = self.request.query_params.get('genre')
        track_id = self.request.query_params.get('trackid')
        if track_id:
            return Track.objects.filter(id=track_id)
        if genre == "ALL":
            queryset = Track.objects.all()
        else:
            queryset = Track.objects.filter(genre=genre)
        track = random.choice(queryset)
        return [track] 

Working solution for mobile and desktop. (Please disregard the token sent in POST)

@api_view(['POST'])
def getTracks(request):
    token = request.data.get('token')
    try:
        test = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
    except:
        return Response('fail', status=status.HTTP_404_NOT_FOUND)

    genre = request.data.get('genre')
    track_id = request.data.get('trackid')
    if track_id:
        return Track.objects.filter(id=track_id)
    if genre == "ALL":
        queryset = Track.objects.all()
    else:
        queryset = Track.objects.filter(genre=genre)
    track = random.choice(queryset)
    new_track = model_to_dict(track)
    return Response(new_track)

Urls.py

from django.urls import path, include
from .api import TrackViewSet, FavoriteViewSet, PurchaseViewSet, getTracks
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
# router.register('tracks', TrackViewSet, basename='tracks') # uncomment for 'error code 'endpoint below
router.register('favorites', FavoriteViewSet, basename='favorites')
router.register('purchases', PurchaseViewSet, basename='purchases')
urlpatterns = [
    path('', include(router.urls)),
    path('getTracks/', getTracks)
]

Not working axios (need to use commented router URLs)

  useEffect(() => {
      const config = {
      headers: {
        "Content-type": "application/json",
         'Authorization':"tokens.access"
        },
      };
      axios.get('tracks?genre=ALL', config)
      .then(res=>setNewTracks(res.data))
      .catch(err=>alert('bad',err.response.statusText))

settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ('rest_framework_simplejwt.authentication.JWTAuthentication',)
}
Andrew-Chen-Wang commented 3 years ago

Your browser doesn't create the token, the server does. So I'm not sure what you're describing. The second piece of code shows you sending a POST request with the token which is not recommended as you should instead just include the token in the Authorization header.

We have an example at the SimpleJwt organization for React.

Antsthebul commented 3 years ago

Thats the problem, if sent as a header 'Authorization: Bearer "[token]"' Then it does not work on mobile browsing. Sorry for my improper wording. I've been trying to post code from multiple test rounds, so things got mixed up. To duplicate this error, these steps need to happen:

Checking logs of server, I get unauthorized 401 error when browsing via mobile.

I need the token to be sent from the front end to the backend. I understand its not recommended and that it should be sent in headers, but considering the method of storing the token, I feel its kind of a moot point, My opinion though or maybe I dont understand the importance of distinguishing HTTP headers vs acutally context data, Im just wondering as to why this is occurring, and maybe someone can shed some light. To recap, If you need to create it's simply done by creating a model viewset on a server and setting the authentication in settings. Then hitting the same endpoint on a mobile browser.

Andrew-Chen-Wang commented 3 years ago

Previously I said to use the Authorization header since you were using local storage. Really, you should probably use cookies and need to enable an option on your request framework e.g. fetch/Axios to actually be able to send it.

Why it's failing on ModelViewSet is beyond me. You need to provide a reproducible example. Or at least tell me the following:

You'll probably need to subclass our authentication.py to see the request object or check your browser console to see which headers or cookies are sent? If you don't see your token in the HTTP request, then there's your problem. Please debug it carefully since I mobile and desktop browsers are basically the same, at least in this context. I haven't seen anyone have a problem with mobile connection before, let alone somehow the api_view be the working version and ModelViewSet not working.

ModelViewSet and api_view are the exact same, so I can't imagine there's really any difference.

Antsthebul commented 3 years ago

Ok so i took the time to fork the repo of https://github.com/SimpleJWT and implement my own quick serializer/router/modelviewset, and it works on mobile. So that rules out the issue of modelviewset. So its most likely to be an error in the applications code but what I find interesting is the different responses in the data attribute between the images below. Keep in mind the invalid token can be corrected, and is not the issue im dealing with, when looking at the desktop image.

Desktop -(again invalid token is OK here, once logged in, this 401 becomes a 200) Screenshot_2

Mobile - (notice the error, but checkout the headers) Screen Shot 2021-08-17 at 3 24 44 PM

Thanks for your help!

Andrew-Chen-Wang commented 3 years ago

@Antsthebul sure... but why do you have double quotes in your Authorization header? It's just Bearer e... not Bearer "e...". Just a slight concern, but it's not a security concern!

Antsthebul commented 3 years ago

@Antsthebul sure... but why do you have double quotes in your Authorization header? It's just Bearer e... not Bearer "e...". Just a slight concern, but it's not a security concern!

I added JSON.stringify(..) when setting the token to localstorage, this was a recent change and even with out the "json conversion" the error still occurred

Andrew-Chen-Wang commented 3 years ago

Just took a second look out the image. Are you sure you disabled the session authentication for DRF? That first error message isn't part of our repository... At least I don't think; it could be from a util function.

Antsthebul commented 3 years ago

Just took a second look out the image. Are you sure you disabled the session authentication for DRF? That first error message isn't part of our repository... At least I don't think; it could be from a util function.

Yup. I've updated the original comment to display the settings.py. The view, as you'll see above, imports permissions from rest_framework and checks "isAuthenticated"