`[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.
--
Ticket URL: <https://code.djangoproject.com/ticket/24465>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
* status: new => closed
* needs_better_patch: => 0
* resolution: => invalid
* needs_tests: => 0
* needs_docs: => 0
Comment:
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/24465#comment:1>
Comment (by erikr):
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
--
Ticket URL: <https://code.djangoproject.com/ticket/24465#comment:2>
Comment (by marklit):
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/24465#comment:3>
Comment (by erikr):
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/24465#comment:4>
Comment (by schinckel):
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/24465#comment:5>
Comment (by cpbotha):
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:
{{{#!python
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/24465#comment:6>