16

I am implementing user authentication with django-rest_framework_simple-jwt with custom user, My models.py:

class UserManager(BaseUserManager):
    def create_user(self, email, username, password, alias=None):
        user = self.model(
        email = self.normalize_email(email),
                username = username,)
        user.set_password(password)
        user.save()
        return user
   def create_superuser(self, email, username, password):
       self.create_user(email, username, password)
       user.is_staff()
       user.is_superuser = True
       user.save()
       return user

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(null=False, unique=True)
    username = models.CharField(max_length=25, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username",]

So I am implementing restframework simple-jwt authentication,my settings .py is as follows as:

REST_FRAMEWORK={
  'DEFAULT_AUTHENTICATION_CLASSES': [
      'rest_framework_simplejwt.authentication.JWTAuthentication',
   ]}

my urls.py:

urlpatterns = [
       url(r'^api/token/$', TokenObtainPairView.as_view(),  name='token_obtain_pair'),
       url(r'^api/token/refresh/$', TokenRefreshView.as_view(), name='token_refresh'),]

on login process, it returns error that "detail": "No active account found with the given credentials" all my users were active. I have no clue to sort this out, I need help.Thanks in advance.

A.JRJ
  • 331
  • 1
  • 5
  • 16

9 Answers9

29

Ensure your password is being hashed before it is stored in your db. I ran into the same problem and discovered my passwords were being stored in plain text. Adding the following to my UserSerializer solved the issue

from django.contrib.auth.hashers import make_password

def validate_password(self, value: str) -> str:
    """
    Hash value passed by user.

    :param value: password of a user
    :return: a hashed version of the password
    """
    return make_password(value)
Keaton Pennels
  • 291
  • 2
  • 4
  • 2
    Thanks I was useful for me. In my case password for hashed twiced since I hashed in validation, like you proposed, and then I used user.set_password which was also hashing it. – pbn Nov 11 '19 at 19:49
  • 1
    Great call! Just remember to first validate whether `value` has any value, because `make_password(None)` and `make_password('')` will also return hashes and it won't trigger the model's "required" validation since you're now sending some value. – Luca Bezerra Oct 26 '20 at 05:08
  • How do I ensure that? Using postgres and data isn't stored in – Audiopolis Jun 30 '22 at 17:17
3

Either you did not create a superuser for your Django application or you are provided the wrong credentials for authentication

3

Also make sure, is_active = True for the user object that you are saving in your serializer, because

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['fullname', 'username', 'email', 'password']


    def create(self, validated_data):
        password = validated_data.pop('password', None)
        instance = self.Meta.model(**validated_data)
        
        # Adding the below line made it work for me.
        instance.is_active = True
        if password is not None:
            # Set password does the hash, so you don't need to call make_password 
            instance.set_password(password)
        instance.save()
        return instance

Note ( As per docs )

The login_required decorator does NOT check the is_active flag on a user, but the default AUTHENTICATION_BACKENDS reject inactive users.

indianwebdevil
  • 4,879
  • 7
  • 37
  • 51
1

Did you remember to set in settings:

AUTH_USER_MODEL = 'your_app_name.User'
Leoog
  • 244
  • 2
  • 8
  • I already added. My app name is posts and I added it as ` 'posts.User'`, still it didnt worked – A.JRJ May 01 '19 at 07:56
  • 1
    Did you try it in postman by passing email and password to the token obtain url? Your error says no active account found with the given credentials meaning you should check your database if users are accurately being stored as active. – Leoog May 01 '19 at 19:44
  • Its not getting logged in, but if we use the super user credentials it gives the token.I am in the stage where I cant signin using normal user.@Leoog – A.JRJ May 03 '19 at 06:35
  • If I could see more of your code and database I could help you fix it but like this we will just end up spending a lot of time and not get anywhere. Maybe you can msg me? – Leoog May 03 '19 at 16:13
  • @Leoog I am facing the same issue. I am able to get auth token from super user credentials but when I try with normal user I am getting "No active account found with the given credentials". – Darshan Jain Sep 13 '20 at 05:21
  • 1
    @DarshanJain check to make sure normal users in the database are being stored as active. Django will not return auth token for inactive users. – Leoog Sep 14 '20 at 17:56
  • @Leoog I found the solution, When I added password into the user serializer, It worked for me. I also added the validate_password function, just to make sure my password is hashed. Thank you – Darshan Jain Sep 15 '20 at 13:39
  • @Leoog your above comment about inactive users solved the issue in my case. I recommend posting it as an answer. thanks – hm6 Dec 02 '20 at 14:06
1

It seems my error was being caused by a write_only parameter on my password field

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


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

Removed it:

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(
        max_length=68, min_length=6)


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

and then it was all sunshine and rainbows after that :-)

apiyo
  • 103
  • 7
0

try this

from django.contrib.auth.hashers import make_password

class UserManager(BaseUserManager):
def create_user(self, email, username, password, alias=None):
    user = self.model(
    email = self.normalize_email(email),
            username = username,)
    user.set_password(make_password(password))
    user.save()
    return user
def create_superuser(self, email, username, password):
   self.create_user(email, username, password)
   user.is_staff()
   user.is_superuser = True
   user.save()
   return user
neomatrixcode
  • 11
  • 1
  • 3
0

Downgraded manually from PyJWT==2.0.0 to PyJWT==1.7.1 and solved our problem

pip install PyJWT==1.7.1

We are using djangorestframework==3.12.1 and djangorestframework-simplejwt==4.4.0 on our requirements.txt and that gave us automatically the 2.0.0 version dependency.

0

In my opinion, there is a problem where an email address and username is provided for the serializer, but an email is expected as a username for authentication. I had the same error too. I also made some kind of custom user and tried to login to get a couple of json web tokens. But I only used email, not username. So what I did in the end and it works for me. Perhaps this example explains something in the place where authentication is done ...

Model and manager like this:

from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import (
    AbstractBaseUser, 
    BaseUserManager
    )
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
from rest_framework_simplejwt.tokens import RefreshToken


class CustomUserManager(BaseUserManager):
    def get_or_create(self, email=None, **kwargs):
        allowed_kwargs = ['first_name', 'last_name', 'img', 'about']
        if email is not None:
            try:
                user_obj = super(CustomUserManager, self).get(email=email)
                if kwargs:
                    for k, v in kwargs.items():
                        setattr(user_obj, k, v)
                    user_obj.save()
            except ObjectDoesNotExist:
                email = self.normalize_email(email)
                user_obj = self.model(email=email)
                password = kwargs.pop('password', None)
                if password is not None:
                    user_obj.set_password(password)
                if kwargs:
                    for k, v in kwargs.items():
                        if k in allowed_kwargs:
                            setattr(user_obj, k, v)
                user_obj.save()
        else:
            return False
        return user_obj
        

class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_('email address'), null=True, unique=True)
    first_name = models.CharField(max_length=150, null=True, blank=True)
    last_name = models.CharField(max_length=150, null=True, blank=True)
    img = models.URLField(null=True, blank=True)
    about = models.TextField(_('about'), max_length=500, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)

    objects = CustomUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def __str__(self):
        return f'<CustomUser(id={self.id} - email={self.email})>'

    class Meta:
        ordering = ['-created_at']
    
    @property
    def full_name(self):
        if hasattr(self, 'first_name') and hasattr(self, 'last_name'):
            return f'{self.first_name} {self.last_name}'
        return 'No fullname'

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

Customizing token serializer:

from django.contrib.auth import authenticate
from django.contrib.auth.models import update_last_login
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import serializers

class CustomTokenSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    email = serializers.EmailField(required=True)
    password = serializers.CharField(min_length=8, write_only=True)
    def validate(self, email, password):
        try:
            self.user = CustomUser.objects.get(email=email)
        except ObjectDoesNotExist as e:
            message = {'error': f'User with email={email} does not exist.'}
            return message
        check_auth = authenticate(username=email, password=password)
        if check_auth is None:
            message = {'error': 
                       'The user exists, but the password is incorrect.'}
            return message
        data = self.user.jwt_tokens
        update_last_login(None, self.user)
        return data

Urls:

urlpatterns += [
    path('login/token/', views.LoginTokenView.as_view(), name='login-token')
]
0

I faced a similar issue. Turns out that I had not included the password field among the fields in the Writer serializer.

Before I had code like this;

class UserWriterSerializer(serializers.ModelSerializer):
    class Meta:
        model = AppUser
        fields = [
            'id',
            'username',
            'first_name',
            'last_name',
            'email',
            'is_active',
            'telephone',
            'userType',
            'gender',
            'registration_date'
        ]

Then I added the password field to have this;

class UserWriterSerializer(serializers.ModelSerializer):
    class Meta:
        model = AppUser
        fields = [
            'id',
            'username',
            'password',
            'first_name',
            'last_name',
            'email',
            'is_active',
            'telephone',
            'userType',
            'gender',
            'registration_date'
        ]

So in summary, some different value was being saved in the database which was not the password I added. Thus having that error, because the password you place as an input is not matching with what is in the database. Make sure that the serializer is correct