#36325 closed Bug (invalid)
Inconsistent error handling for inactive users in ModelBackend
Description ¶
### 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
- Create a user with
is_active=False
. - Attempt to log in using the correct credentials (e.g., via Django Admin).
- 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.
Change History (3)
by , 10 days ago
Attachment: | image-20250413-153205.png added |
---|
comment:1 by , 10 days ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
Hello! Thanks for the report.
Returning a generic error message is desirable from a security perspective and is recommended by OWASP: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#login.
The use case you are describing goes against the security practices. Closing this ticket because the use case is not aligned with the security practices.
If you need assistance in using Django please feel free to ask for help from a friendly community member on Discord or the Django forum