I am creating DRF authentication APIs for Abstract Base users in my Django project and 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 are my login
and verification
views:
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 login
serializer:
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.
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.