Search code examples
pythondjangodjango-authentication

Django: How do I authenticate an AbstractBaseUser?


I have created a model for an AbstractBaseUser, but am struggling to authenticate it.

Here is my model:

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.contrib.auth.password_validation import validate_password

from uuid import uuid4


class CustomUserManager(BaseUserManager):
    def _create_user(self, email, password, is_staff=False, is_superuser=False, **other_fields):
        if not email:
            raise ValueError('Email address must be specified')

        if not password:
            raise ValueError('Password must be specified')

        user = self.model(
                    email=self.normalize_email(email),
                    is_staff=is_staff,
                    is_superuser=is_superuser,
                    **other_fields
                )
        validate_password(password)
        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_user(self, email, password, **other_fields):
        return self._create_user(email, password, False, False, **other_fields)

    def create_superuser(self, email, password, **other_fields):
        return self._create_user(email, password, True, True, **other_fields)


class CustomUser(AbstractBaseUser):
    id = models.UUIDField(primary_key=True, default=uuid4(), editable=False, unique=True)
    email = models.EmailField(max_length=254, unique=True)
    
    is_superuser = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=True)
    is_active = models.BooleanField(default=True)
    is_premium = models.BooleanField(default=False)

    first_name = models.CharField(max_length=40)
    last_name = models.CharField(max_length=40)
    display_name = models.CharField(max_length=40)

    date_of_birth = models.DateField()
    currency = models.CharField(max_length=3)

    date_joined = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)

    REQUIRED_FIELDS = ['first_name', 'display_name', 'date_of_birth', 'currency']
    USERNAME_FIELD = 'email'
    EMAIL_FIELD = 'email'

    objects = CustomUserManager()

    def get_full_name(self):
        return ("%s %s" % (self.first_name, self.last_name)) if self.last_name != '' else self.first_name

    def get_short_name(self):
        return self.first_name

    def __str__(self):
        return "%s %s - %s" % (self.first_name, self.last_name, self.email)

Here is my view:

from django.contrib.auth import authenticate
from django.http import HttpResponse


def user_login(request):
    user = authenticate(request, email=request.POST['email'], password=request.POST['password'])

    if user is not None:
        login(request, user)
    else:
        HttpResponse("Invalid email/password pair", status=401)

    return HttpResponse()

And, in my settings.py, I have the following:

AUTH_USER_MODEL = 'myapp.CustomUser'

The issue is that, whenever I send create a user and send a request to user_login, the authenticate method always returns None, thus triggering the HttpResponseBadRequest. The email in the request matches what is in the database, and the password matches the password I used to create the user (I've spent longer than I care to admit using print statements to verify that).

Is there something I am missing? Do AbstractBaseUsers require something extra for authentication?


Solution

  • The comment by Iain Shelvington clued me into the answer.

    The test I was using to test the authentication was creating the user by saving it directly to the database like this:

    from .models import CustomUser
    
    
    user_data = generate_user()
    CustomUser(**user_data).save()
    

    Because the user was being saved directly to the database, its password never got hashed.

    I needed to instead create the user like this:

    from Django.test import Client
    from .models import CustomUser
    
    
    user_data = generate_user()
    
    client = Client()
    client.post('/endpoint/used/to/create/user', user_data)