6

I've created a JWT-Authorised back end for an app. Login, logout, token retrieval and refresh all work fine, and as expected. I added a registration view this morning, which is throwing the usual "detail": "Authentication credentials were not provided. error you'd expect for non-authenticated requests, as that's the default (see below).

However, because this is a registration endpoint, I don't want it to only allow authorised requests. (Having checked with a valid token, the rest of the view works as expected when you supply authentication.) Looking at the permissions section of the DRF docs, I thought that using the permission_classes wrapper with AllowAny would work here, but it hasn't.

What am I missing? I feel like the permission_classes decorator should override the default setting of 'IsAuthenticated'?

I'm testing on localhost from curl:

curl -X POST -H "Content-Type: application/json" -d '{"email":"boba@athingy09876.com", "first_name": "boba", "last_name": "fett" "password":"xyz"}' http://localhost:8000/account/register/

View is:

@permission_classes(AllowAny)
@api_view(['POST'])
def register_user(request):
    from django.contrib.auth.models import User
    from rest_framework_jwt.views import obtain_jwt_token
    if request.user.is_authenticated():
        return Response ({"already_registered": "User with that username has already registered"}, status=status.HTTP_701_ALREADY_REGISTERED)
    data = request.data

    user, created = User.objects.get_or_create(username=data["email"],
                                               email=data["email"],
                                               first_name=data["first_name"],
                                               last_name=data["last_name"],
                                               password=data["password"])
    if created:
        token = obtain_jwt_token(data["email"],data["password"] )
        return Response ({"token": token}, status=status.HTTP_200_OK)
    else:
        return Response ({"already_registered": "User with that username has already registered"}, status=status.HTTP_701_ALREADY_REGISTERED)

Permissions in settings.py are:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ),
}

Related questions: Django Rest Framework - Authentication credentials were not provided - I think the default permissions are correct, I just want to override them in this instance.

Django Rest Framework - DELETE ajax call failure due to incorrect CSFR token - CSRF not being used as JWT Based auth.

Django: Rest Framework authenticate header - Apache specific issue (currently still on devserver localhost)

Django Rest Framework Authentication credentials were not provided - Not yet answered!

Community
  • 1
  • 1
Withnail
  • 3,128
  • 2
  • 30
  • 47
  • Did my answer work for you? – Jaakko Apr 07 '15 at 19:16
  • Hey! I haven't had a chance to implement it yet, but it looks good - i'll get to it tomorrow, before the bounty expires. :) Sorry about the delay! – Withnail Apr 07 '15 at 21:10
  • 1
    Totally unclear as to why the question's being voted down, btw - if there's a problem with the question, then do let me know, it's not like it was a half-baked, unresearched question. – Withnail Apr 08 '15 at 19:21

3 Answers3

6

The order of the decorators matter. There's also some problems with your code.

I recommend using a serializer, maybe something like below. If you want to use emails as username, I would make a custom User model. Django's default authentication system's username field has max_length of 30, and people's email addresses easily surpass that.

class UserSerializer(serializers.ModelSerializer):
    first_name = serializers.CharField(required=False, allow_null=True)
    last_name = serializers.CharField(required=False, allow_null=True)
    class Meta:
        model = User
        fields = ('id', 'username', 'first_name', 'last_name', 'email', 'password')

    def create(self, validated_data):
        return User.objects.create_user(**validated_data)

@api_view(['POST'])
@permission_classes([permissions.AllowAny,])
def register_user(request):
    if request.user.is_authenticated():
        return Response({"already_registered": "User with that username has already registered"}, status=701)
    data = request.data
    serializer = UserSerializer(data=data, partial=True)
    if serializer.is_valid():
        serializer.save(username=serializer.validated_data['email'])
        token = #call the url to get your tokens, use urllib or something similar
        return Response({"token": token}, status=status.HTTP_201_CREATED)
    else:
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Edit The ordering of decorators goes like this:

@decorator
@decorator2
def func():
    print('hello world')

Is the same as decorator(decorator2(func)))

Jaakko
  • 584
  • 6
  • 13
  • Oh man. I changed the order of the decorators, and boom, there you go. I couldn't get the serialized version to work off the bat, but I can see what you mean, so I'll have a crack at redoing it that way tomorrow using this as a jumping off point. Thanks! Any ideas where would I look to find out more about ordering decorators like this? – Withnail Apr 08 '15 at 19:20
  • 1
    added something about decorators... tried putting here but the code looked horrendous on the comments... – Jaakko Apr 08 '15 at 21:36
4

You have disabled permissions using @permission_classes, but that's only the "authorization" part of "authentication and authorization". You need to disable the authentication handlers as well using @authentication_classes in order to stop receiving a 401/403 error.

Kevin Brown-Silva
  • 40,873
  • 40
  • 203
  • 237
  • Sorry, I don't really understand what you mean. From reading that, suggests that when the auth fails as expected it should provide an AnonymousUser at request.user - but according to the permissions stuff, it shouldn't matter, @permission_classes(AllowAny) should allow unrestricted access. In any case, I've tried with @authentication_classes((SessionAuthentication, BasicAuthentication)) to no avail, and can't see anything in restframework.authentication that covers this use case - do I need to extend the baseclass for this? Seems overkill... (As in, I'm mostly sure I just don't understand) – Withnail Mar 29 '15 at 18:32
  • I've also gone in and set `REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [], 'DEFAULT_AUTHENTICATION_CLASSES': [], }` Which seems to work, but now I have to wrap every other function in explicit decorators, rather than using the default behaviour, which seems a bit backwards? Surely there's a (clearer?) way to disable the auth handlers. I've been through the docs you linked several times and I just don't see it. :( – Withnail Mar 29 '15 at 19:07
0

in views

from rest_framework_simplejwt.authentication import JWTAuthentication

and use this in class based view or function based view

authentication_classes = [JWTAuthentication]

for example

class CreateProduct(generics.CreateAPIView):
authentication_classes = [JWTAuthentication]
permission_classes = [permissions.IsAuthenticated]
queryset = Product.objects.all()
serializer_class = ProductsSerializer