Search code examples
pythondjangohttphttp-status-codes

Django login with wrong credentials returns 200 not 401


This is a pretty straight forward test, but I can't seem to get it right.

I want to check which users can login and perform actions (it's part of a larger suite of tests), but the very first step causes some issues.

class SuperUserTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.su = User.objects.create_superuser('super','','the_correct_password')
    def test_su_can_login(self):
        response = self.client.post(reverse('django.contrib.auth.views.login'),
            {'username': 'super', 'password': 'the_wrong_password'})
        self.assertEqual(response.status_code,401)

        # Success redirects to the homepage, so its 302 not 200
        response = self.client.post(reverse('django.contrib.auth.views.login'),
            {'username': 'super', 'password': 'the_correct_password'})
        self.assertEqual(response.status_code,302)

When I run the test I get:

(my_app)00:20 ~/my_app (master)$ ./manage.py test my_app.SuperUserTest
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_su_can_login (my_app.SuperUserTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./my_app/tests.py", line 341, in test_su_can_login
    self.assertEqual(response.status_code,401)
AssertionError: 200 != 401

----------------------------------------------------------------------
Ran 1 test in 1.180s

FAILED (failures=1)
Destroying test database for alias 'default'...

Why is django returning HTTP code 200 when I incorrectly login?

For additional context, here is how I'm managing the login/logout urls:

urlpatterns = patterns('',
    # Examples:
    url(r'^accounts/login/?$', 'django.contrib.auth.views.login'),
    url(r'^accounts/logout/?$', 'django.contrib.auth.views.logout',
        {'next_page': '/'}),

Solution

  • There's some debate in the web community about what the right response to a credentials failure is. For example, here's a Wordpress ticket about switching from 200 to 401. Here on Stack Overflow you can find some recommending 200 and some recommending 401.

    Django chooses to return a 200 response along with the re-rendered form.

    In my opinion this is the right approach. A 401 or 403 response indicates that the user doesn't have permission to access the resource (URL). But in this case the resource is the login point, and you don't need credentials to access that; by definition it's available to all users. So essentially this case is no different from any other form validation—the server is checking the inputs it was sent, and if they're invalid it returns a 200 response along with the form and an error message.