简体   繁体   中英

How to solve error 401 unauthorized login in DRF simple jwt user login

I am creating DRF authentication APIs for Abstract Base users in my Django project and I am using simple JWT. The registration and email verification APIs work fine, but when I try to log in using the credentials of a valid user, I get an error 401 unauthorized access.

the custom user model in models.py

class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=255, unique=True,db_index=True)
    email = models.EmailField(max_length=255, unique=True,db_index=True)
    is_verified = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now_add=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELD = ['username']

    objects = UserManager()

    def __str__(self):
        return self.email

    def tokens(self):
        refresh = RefreshToken.for_user(self)
        return{
            'refresh':str(refresh),
            'access': str(refresh.access_token)
        }

Here is my views.py :

class RegisterView(generics.GenericAPIView):

    serializer_class = RegisterSerializer
    
    def post(self, request):
        user = request.data
        serializer = self.serializer_class(data=user)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        user_data = serializer.data
        user = User.objects.get(email=user_data['email'])

        token = RefreshToken.for_user(user).access_token

        current_site = get_current_site(request).domain
        relativeLink = reverse('email-verify')

        absurl = 'http://' + current_site + relativeLink + "?token=" + str(token)
        email_body = "Hi " + user.username+ 'Use the link below to verify your email \n' + absurl
        data = {'email_body': email_body,'to_email': user.email,
         'email_subject':'Verify your email'}
        Util.send_email(data)

 
        return Response(user_data, status=status.HTTP_201_CREATED)

class VerifyEmail(views.APIView):
    serializer_class = EmailVerificationSerializer

    token_param_config = openapi.Parameter('token',in_=openapi.IN_QUERY, description='Description', type=openapi.TYPE_STRING)

    @swagger_auto_schema(manual_parameters=[token_param_config])
    def get(self, request):
        token = request.GET.get('token')
        try:
            payload = jwt.decode(token,settings.SECRET_KEY, algorithms=['HS256'])
            user = User.objects.get(id=payload['user_id'])
            if not user.is_verified:
                user.is_verified = True
                user.save()
            return Response({'email': 'Succesfully activated'}, status = status.HTTP_200_OK)

        except jwt.ExpiredSignatureError as identifier:
            return Response({'error': 'Activation Expired'}, status= status.HTTP_400_BAD_REQUEST)
        except jwt.exceptions.DecodeError as identifier:
            return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST)
            
class LoginAPIView(generics.GenericAPIView):
    serializer_class = LoginSerializer
    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)

        return Response(serializer.data, status=status.HTTP_200_OK)

Here is my serializers.py

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        max_length=68, min_length=8, write_only=True)
  

    class Meta:
        model = User
        fields= ['email', 'username','password']

    def validate(self, attrs):
        email = attrs.get('email', '')
        username = attrs.get('username', '')
  
        if not username.isalnum():
            raise serializers.ValidationError(
                'The username should only contain alphanumeric charachters')
        return attrs

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

class EmailVerificationSerializer(serializers.ModelSerializer):
    token = serializers.CharField(max_length= 555)

    class Meta:
        model = User
        fields = ['token']

class LoginSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(max_length=255, min_length=3)
    password = serializers.CharField(max_length=68, min_length=8, write_only=True)
    username = serializers.CharField(max_length=255, min_length=3, read_only = True)
    tokens = serializers.CharField(max_length=68, min_length=8, read_only = True)


    class Meta:
        model = User
        fields = ['email', 'password', 'username', 'tokens']


    def validate(self, attrs):
        email = attrs.get('email', '')
        password = attrs.get('password', '')
  
        user = auth.authenticate(email=email, password=password)

        if not user:
            raise AuthenticationFailed('Invalid Credentials, try again!')
        if not user.is_active:
            raise AuthenticationFailed('Acccount disabled, please contact admin')
        if not user.is_verified:
            raise AuthenticationFailed('Email is not verified')
                
        return {
            'email': user.email,
            'username': user.username,
            'tokens': user.tokens
        }
        
        return super().validate(attrs)

So the error raised is "Invalid credentials" meaning that details of the user don't exist, while the users are actually there when I check the database. 在此处输入图像描述

Anyone, please help.

What you want is to exclude your login view from the project-wide authentication check that you have added in settings.py.

class LoginAPIView(generics.GenericAPIView):
    permission_classes = ()
    authentication_classes = ()
    serializer_class = LoginSerializer

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)

        return Response(serializer.data, status=status.HTTP_200_OK)

So after a lot of googling and headaches, I ended up reading the Simple Jwt documentation again and as it turns out, the 401 error occurs if the user is not active. In my models.py above, by default, my user's is_verified and is_active are False . The VerifyEmail view changed the is_verified to True after the users verified their emails but their is_active remained False hence giving the error 401 . My solution was adding an is_active=True for when the user verifies their email: Here is the VerifyEmail view in views.py

class VerifyEmail(views.APIView):
    serializer_class = EmailVerificationSerializer

    token_param_config = openapi.Parameter('token',in_=openapi.IN_QUERY, description='Description', type=openapi.TYPE_STRING)

    @swagger_auto_schema(manual_parameters=[token_param_config])
    def get(self, request):
        token = request.GET.get('token')
        try:
            payload = jwt.decode(token,settings.SECRET_KEY, algorithms=['HS256'])
            user = User.objects.get(id=payload['user_id'])
            if not user.is_verified:
                user.is_verified = True
                user.is_active = True           # New
                user.save()
            return Response({'email': 'Succesfully activated'}, status = status.HTTP_200_OK)

        except jwt.ExpiredSignatureError as identifier:
            return Response({'error': 'Activation Expired'}, status= status.HTTP_400_BAD_REQUEST)
        except jwt.exceptions.DecodeError as identifier:
            return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST)

This worked for me.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM