[Django] #24465: Failed logins are recorded as HTTP 200 instead of HTTP 403

12 views
Skip to first unread message

Django

unread,
Mar 10, 2015, 7:19:06 AM3/10/15
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-------------------------------+--------------------
Reporter: marklit | Owner: nobody
Type: Bug | Status: new
Component: contrib.admin | Version: 1.7
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------
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.

--
Ticket URL: <https://code.djangoproject.com/ticket/24465>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Mar 10, 2015, 7:37:43 AM3/10/15
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-------------------------------+--------------------------------------
Reporter: marklit | Owner: nobody
Type: Bug | Status: closed
Component: contrib.admin | Version: 1.7
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------
Changes (by erikr):

* 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>

Django

unread,
Mar 10, 2015, 7:40:21 AM3/10/15
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-------------------------------+--------------------------------------
Reporter: marklit | Owner: nobody
Type: Bug | Status: closed
Component: contrib.admin | Version: 1.7
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------

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>

Django

unread,
Mar 10, 2015, 9:07:54 AM3/10/15
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-------------------------------+--------------------------------------
Reporter: marklit | Owner: nobody
Type: Bug | Status: closed
Component: contrib.admin | Version: 1.7
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------

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>

Django

unread,
Mar 10, 2015, 9:12:46 AM3/10/15
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-------------------------------+--------------------------------------
Reporter: marklit | Owner: nobody
Type: Bug | Status: closed
Component: contrib.admin | Version: 1.7
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------

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>

Django

unread,
Apr 9, 2015, 8:20:55 PM4/9/15
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-------------------------------+--------------------------------------
Reporter: marklit | Owner: nobody
Type: Bug | Status: closed
Component: contrib.admin | Version: 1.7
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-------------------------------+--------------------------------------

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>

Django

unread,
Jan 27, 2020, 11:11:19 AM1/27/20
to django-...@googlegroups.com
#24465: Failed logins are recorded as HTTP 200 instead of HTTP 403
-----------------------------------+--------------------------------------
Reporter: Mark Litwintschik | Owner: nobody
Type: Bug | Status: closed
Component: contrib.admin | Version: 1.7
Severity: Normal | Resolution: invalid
Keywords: | Triage Stage: Unreviewed

Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0

Easy pickings: 0 | UI/UX: 0
-----------------------------------+--------------------------------------

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>

Reply all
Reply to author
Forward
0 new messages