Skip to content
Advertisement

Django differentiate between incorrect login information and inactive user on login

Currently I added in my site a method for email confirmation when registering. What I saw though, is that when the user is registered, but didn’t click in the confirmation link yet and tries to login, I can’t differentiate between wrong user/password and not confirmed user.

This is my login function:

def loginUser(request):
    if request.user.is_authenticated:
        return redirect("decks:index")
    if request.method == "POST":
        form = AuthenticationForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(username=username, password=password)
            if user is not None and user.is_active:
                login(request, user)
                return redirect("myApp:portal")
            elif user is not None:
                messages.error(request, "Email is not confirmed.")
            else:
                messages.error(request, "Invalid username or password.")
        else:
            messages.error(request, "Invalid username or password.")
    form = AuthenticationForm()
    return render(request, "myApp/login.html", context = {'login_form': form})

The problem is that when running form.is_valid() the authenticate function from /home/nowork/.local/lib/python3.8/site-packages/django/contrib/auth ModelBackend is executed:

def authenticate(self, request, username=None, password=None, **kwargs):
    if username is None:
        username = kwargs.get(UserModel.USERNAME_FIELD)
    if username is None or password is None:
        return
    try:
        user = UserModel._default_manager.get_by_natural_key(username)
    except UserModel.DoesNotExist:
        # Run the default password hasher once to reduce the timing
        # difference between an existing and a nonexistent user (#20760).
        UserModel().set_password(password)
    else:
        if user.check_password(password) and self.user_can_authenticate(user):
            return user

So the form.is_valid() will never be true when the is_active flag is False. So I have no way of telling if the combination of user+password is incorrect or the user is not confirmed yet.

I’m not sure what’s the correct way of doing this, I thought to do something like:

User.objects.get(username=request.POST['username'])

Can users exploit this somehow? Is there any better way to accomplish this?

Using python3 and Django 4.0

Advertisement

Answer

You can make your own CustomLoginBackend as

from django.contrib.auth import get_user_model

class CustomLoginBackend(object):

    def authenticate(self, request, username, password):
        User = get_user_model()
        try:
            user = User.objects.using(db_name).get(username=username)
        except User.DoesNotExist:
            return None
        else:
            if user.check_password(password):
                return user
        return None

Then in your views.py

def loginUser(request):
    if request.user.is_authenticated:
        return redirect("decks:index")
    if request.method == "POST":
        form = AuthenticationForm(request, data=request.POST)
        if form.is_valid():
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            user = authenticate(username=username, password=password)
            if user is not None:
                if user.is_active == True:
                    login(request, user)
                    return redirect("myApp:portal")
                else:
                    messages.error(request, "Email is not confirmed.")
            else:
                messages.error(request, "Invalid username or password.")
        else:
            messages.error(request, "Invalid username or password.")
    form = AuthenticationForm()
    return render(request, "myApp/login.html", context = {'login_form': form})

And at last don’t forgot to add AUTHENTICATION_BACKENDS in your settings.py as

AUTHENTICATION_BACKENDS = ['path_to_your.CustomLoginBackend ',]
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement