﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
36325	Inconsistent error handling for inactive users in ModelBackend	Ariel Souza		"### Bug Description

In the `ModelBackend`, the `authenticate` method checks whether `user_can_authenticate(user)` returns `True` before calling `confirm_login_allowed`. This causes `authenticate()` to return `None` silently if the user is inactive (`is_active=False`), even when the credentials are valid.

As a result, any login attempt with an inactive user results in a generic ""email or password is incorrect"" error message. This is especially misleading in the Django Admin, where users expect a specific message indicating that the account is inactive.

### Proposed Solution

Refactor the logic in `authenticate()` to ensure that `confirm_login_allowed(user)` is always called after the user is found and credentials are valid, regardless of the user's active status. This way, custom validation errors (such as inactive account warnings) are properly raised.

### Steps to Reproduce

1. Create a user with `is_active=False`.
2. Attempt to log in using the correct credentials (e.g., via Django Admin).
3. Observe that the system returns a generic ""email or password is incorrect"" error.

### Expected Behavior

A clear message should be displayed indicating that the account is inactive, as raised by `confirm_login_allowed`.


### My comment

I removed the self.user_can_authenticate(user) function from ModelBackend, which solves the problem,

{{{
 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):
                return user
}}}


but I couldn't create the test because the test always gave an error in self.user_can_authenticate(user) of the ModelBackend, returning True for the inactive user. Since I started studying Django recently and I'm currently unemployed, I'll leave this up to you.

Here's my code:

{{{
def test_get_inactive_login_error(self):
        User.objects.create_user(username=""testinactive"", password=""pwd"", is_active=False)
        data = {
            ""username"": ""testinactive"",
            ""password"": ""pwd"",
        }
        form = AuthenticationForm(None, data)
        self.assertIn(""This account is inactive."", form.non_field_errors())
}}}


Thank you for reading this far."	Bug	closed	contrib.auth	5.2	Normal	invalid	ModelBackend;authenticate	Ariel Souza	Unreviewed	0	0	0	0	0	0
