pythondjangoauthenticationexceptioncustom-authentication

Why does default authentication backend of django calls set_password after raising UserModel.DoesNotExist exception?


I am writing a custom auth backend for a custom user model inherited from AbstractUser in django. I have gone through default auth backend of django and saw that user object is fetched using username with UserModel._default_manager.get_by_natural_key(username) authenticate() is as below

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

I have already written a custom aut backend, but I am asking this out of curiosity, Why is there a check on password upon a DoesNotExist exception?

I have tried ipdb in authenticate method and verified that upon getting username of a non-existing user, control flow goes to except block. And a UserModel().set_password(password) was issued in terminal. Being not sure what happens in the background, I inspected code of set_password() of auth.models. But it was just raising a NotImplementedError exception. I am not sure how this helps.

Also how to raise a UserDoesNotExist or django.core.exceptions.ObjectDoesNotExist exception correctly upon a failed object fetch operation on User Model in a custom auth backend? Idea is to stop the execution and present the user with the right feedback message instead of raising an exception and trying out next authentication backend as given in AUTHENTICATION_BACKENDS() in settings.py.

TIA


Solution

  • The comment explains exactly why. If it immediately returned False, it would take much less time than for a user that did not exist. An attacker would then be able to tell the difference between an existing and a non-existing username.