#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 |
Description
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 , 10 years ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
comment:2 by , 10 years ago
For completeness, this type of form handling is documented in: https://docs.djangoproject.com/en/1.7/topics/forms/#the-view.
And if your issue is that you'd like to separately like to record failed logins, you could use: https://docs.djangoproject.com/en/1.7/ref/contrib/auth/#django.contrib.auth.signals.user_login_failed
comment:3 by , 10 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.
comment:4 by , 10 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 , 10 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 , 5 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 urls.py
Thanks to rudi@zatech for putting me on the right path.
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.