Opened 9 years ago

Closed 9 years ago

Last modified 4 years ago

#24465 closed Bug (invalid)

Failed logins are recorded as HTTP 200 instead of HTTP 403

Reported by: Mark Litwintschik Owned by: nobody
Component: contrib.admin Version: 1.7
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no


Attempting to login to the Django admin with an incorrect username and password combination logs the event as an HTTP 200:

[10/Mar/2015 10:24:06] "POST /admin/login/?next=/admin/ HTTP/1.0" 200 2074

I would expect that it would be recorded as a 403.

django.contrib.admin.forms.AdminAuthenticationForm raises a forms.ValidationError if the login is invalid but there is nothing out of the box that will record the response as HTTP 403.

Change History (6)

comment:1 by Sasha Romijn, 9 years ago

Resolution: invalid
Status: newclosed

I can see your point, but the behaviour is correct and intentional. The 200 response means the same form is re-rendered, now including an error message. The user can edit their input and try again. A 403 response would cause the browser to display a much less friendly error, without offering a reasonable way for a user to correct their error.

Basically, login errors are handled the same way as any other form error, in the typical case: return a 200 response, display the form again, prefilled with any non-secret data the user already entered, and the appropriate error message. The user adjusts their input and submits the form again.

comment:2 by Sasha Romijn, 9 years ago

For completeness, this type of form handling is documented in:

And if your issue is that you'd like to separately like to record failed logins, you could use:

comment:3 by Mark Litwintschik, 9 years ago

My concern is that this gives a chance to run a dictionary attack against the login. If fail2ban is monitoring nginx's logs or django's logs directly the response for a failed login and a correct one look the same.

Thanks for the heads up on django.contrib.auth.signals.user_login_failed, I'll take a look at it.

Last edited 9 years ago by Mark Litwintschik (previous) (diff)

comment:4 by Sasha Romijn, 9 years ago

There are a few third party packages available that add brute-forcing protection to Django (login) forms: best known are django-axes and django-lockout. These are able to detect exactly this type of login failure and take a certain action after a certain number of attempts in a certain time. I use django-axes myself in many projects.

Alternatively, you could use the user_login_failed signal to write a log message to the log of your Django app, and have fail2ban trigger on those log messages.

comment:5 by Matthew Schinckel, 9 years ago

I've found that using a 401 does not give an error page, but still allows you to see in the logs that it was a non-successful login attempt.

comment:6 by cpbotha, 4 years ago

Just in case anyone else also spends hours trying to figure out how to get the LoginView to return a response with HTTP status 401 instead of 200:

The call hierarchy is: LoginView.form_invalid() -> TemplateResponseMixin.render_to_response() -> TemplateResponse -> SimpleTemplateResponse -> HttpResponse -> HttpResponseBase which has the default status code 200

To change to 401, which still just re-renders the form, but now nicely logs the 401, do something like this:

from django.contrib.auth.views import LoginView
class LoginView401(LoginView):
    def form_invalid(self, form):
        return self.render_to_response(self.get_context_data(form=form), status=401)

... and then use LoginView401 in your

Thanks to rudi@zatech for putting me on the right path.

Note: See TracTickets for help on using tickets.
Back to Top