Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 391
cutteeth Avatar asked Feb 07 '23 21:02

cutteeth


2 Answers

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.

like image 166
Daniel Roseman Avatar answered Feb 09 '23 12:02

Daniel Roseman


You should read this:

https://code.djangoproject.com/ticket/20760

When attempting to authenticate using django.contrib.auth, if a user does not exist the authenticate() function returns None nearly instantaneously, while when a user exists it takes much longer as the attempted password gets hashed and compared with the stored password. This allows for an attacker to infer whether or not a given account exists based upon the response time of an authentication attempt.

like image 20
Destrif Avatar answered Feb 09 '23 11:02

Destrif