Login with either membership_number, phone_number or email

I’m implementing a User model that can login with either a membership number, phone number or email but can’t seem to make it work. Any help as to what I’m doing wrong or how to implement it right.

I’m able to create a user, but when I login with the credentials, I get “invalid login credentials”

I have exhausted all my options on what to do.

  1. I first created a custom user model and set the USERNAME_FIELD to “membership_number” and the REQUIRED_FIELDS to “email” and “phone_number”
class User(BaseModel, AbstractBaseUser, PermissionsMixin):
    """
    Custom user model.
    """

    membership_number = models.CharField(
        _("membership number"),
        max_length=100,
        unique=True,
        help_text=_("Membership number is same as Baptism Number(nlb)."),
    )
    first_name = models.CharField(_("first name"), max_length=150)
    last_name = models.CharField(_("last Name"), max_length=150)
    middle_name = models.CharField(_("middle name"), max_length=150, blank=True, default="")
    email = models.EmailField(_("email address"), unique=True, blank=True, null=True)
    phone_number = PossiblePhoneNumberField(_("phone number"), unique=True, blank=True, null=True)

    objects = UserManager()

    USERNAME_FIELD = "membership_number"
    REQUIRED_FIELDS = ["email", "phone_number"]

    class Meta:
        ordering = ("created_at",)
        verbose_name = _("user")
        verbose_name_plural = _("users")
        app_label = "account"

        constraints = [
            models.UniqueConstraint(
                fields=["membership_number", "email", "phone_number"],
                name="unique_membership_number_email_phone",
            )
        ]
  1. Created a manager for the custom user
class UserManager(BaseUserManager):
    """
    Custom user model manager where membership_number is the unique identifiers
    for authentication.
    """

    def create_user(
        self, membership_number, email=None, phone_number=None, password=None, **extra_fields
    ):
        """
        Create and save a User with given membership_number and password.
        """
        email = self.normalize_email(email) if email else None
        user = self.model(
            email=email,
            membership_number=membership_number,
            phone_number=phone_number,
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(
        self, membership_number, email=None, phone_number=None, password=None, **extra_fields
    ):
        """
        Create and save a SuperUser with the given membership_number and password.
        """

        user = self.create_user(membership_number, email, phone_number, password, **extra_fields)
        user.is_superuser = True
        user.is_admin = True
        user.is_staff = True
        user.save(using=self._db)
        return user
  1. Created a custom backend for the custom user model
rom django.contrib.auth.backends import BaseBackend, ModelBackend
from django.db.models import Q

from apps.account.models import User


class CustomBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            user = User.objects.filter(
                Q(email=username) | Q(phone_number=username) | Q(membership_number=username)
            ).first()
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

    def get_user_by_username(self, username):
        try:
            return User.objects.get(
                Q(email=username) | Q(phone_number=username) | Q(membership_number=username)
            )
        except User.DoesNotExist:
            return None
  1. created a login serializer using DRF and django rest framework simple jwt
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        credentials = {
            "username": attrs.get("username"),
            "password": attrs.get("password"),
        }
        user = CustomBackend().authenticate(self.context["request"], **credentials)
        if not user:
            raise serializers.ValidationError("Invalid login credentials")
        if not user.is_active:
            raise serializers.ValidationError("User account is disabled")
        data = super().validate(attrs)
        data["username"] = user.username
        return Response(
            {
                "data": data,
                "success": "Successfully logged in.",
            }
        )
  1. login api view
class CustomTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

To help track this down, I would either running this in the debugger or adding some print statements in your authenticate method to verify what is happening at each step. Perhaps one immediately after the function def line to print the username being submitted and possibly even the password. Then another one after the query to see if a user object was returned, along with one in the except block to verify whether the exception was thrown.

That should help narrow down where the problem may be occurring.

1 Like