Search code examples
djangodjango-rest-frameworkdjango-rest-auth

Django Rest Framework returns 200 OK even though login request has incorrect credentials


This is my URLS.py:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^CMS/', include('CMSApp.urls')),
    url(r'^admin/', include(admin.site.urls)),
]

and this is CMSApp/urls.py:

from django.conf.urls import url
from django.conf.urls import include
from CMSApp import views

urlpatterns = [
    url(r'^$', views.HomePageView.as_view()),
    url(r'^users$', views.user_list.as_view()),
    url(r'^users/(?P<pk>[0-9]+)$', views.user_detail.as_view()),
    url(r'^api-auth/', include('rest_framework.urls',
                                   namespace='rest_framework')),
]

My HomePageView serves this home.html page:

<h3>Register</h3>
<form ng-submit="ctrl.add()" name="myForm">
    <label>Username</label>
    <input type="text" name="uname" ng-model="ctrl.user.username" required> 
    <label>Password</label>
    <input type="password" name="pwd" ng-model="ctrl.user.password" required>
    <label>Email</label>
    <input type="email" name="mail" ng-model="ctrl.user.email" required> 
    <input type="submit" value="Register"> 
</form>

<h3>Login</h3>
<form ng-submit="ctrl.loginUser()" name="myLoginForm">
    <label>Username</label>
    <input type="text" name="uname" ng-model="ctrl.user.username" required> 
    <label>Password</label>
    <input type="password" name="pwd" ng-model="ctrl.user.password" required>
    <input type="submit" value="Login"> 
</form>

<script>
angular.module("notesApp", [])
    .config(['$httpProvider', function($httpProvider) {
        $httpProvider.defaults.xsrfCookieName = 'csrftoken';
        $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
    }])

    .controller("MainCtrl", ["$http", function($http) {
    var self = this;
    self.users = {};
    var fetchUsers = function() {
        return $http.get("/CMS/users").then(function(response) { // get list of existing users
            self.users = response.data;
        }, function(errResponse) {
        console.error("Error while fetching users.");
        });
    };

    self.loginUser = function() {
        $http.post("/CMS/api-auth/login/", self.user)
        .error(function(data, status, headers, config) {
            console.log(data);
         })
        .then(fetchUsers);

        console.log("Success Login with ", self.user);
    };
    }]);
</script>

When I register a user which already exists, then Django returns a 400 Bad Request error, which is good because I use AngularJS on the frontend to handle the 400 status code.

The issue is, when I try to log in with an incorrect username and password (a username which does not exist or a username and password which do not match), Django returns a 200 OK status code so I can't track the issue using AngularJS on the frontend. Any idea why Django returns a 200 OK when I try to log in with an incorrect username and password? How do I get Django to return a 400 when an incorrect username / password is submitted?

Edit: This was my original post: Django Rest Framework + AngularJS not logging user in or throwing errors and charlietfl commented on it saying "as for the ajax error handling. If login fails .... using true REST methodology would send back a 401 status which would then fire your ajax error handler", which is why I do not want a 200 OK when login fails.


Solution

  • The first thing to understand is that Django does not by default enforce authentication.. you can use the auth module to enroll users and to authenticate them, but you have to protect your views explicitly. The authentication app only provides API's for you to use, it doesn't actually protect anything unless you use those API's.

    Any view that isn't explicitly checked for authentication will be open to anyone.

    Sure, admin requires you to log in, but that's because the authors of the admin app included checks in their code...

    The Django REST Framework has it's own checks for this, so very little coding needed, you just have to configure each view (see docs):

    http://www.django-rest-framework.org/api-guide/authentication/

    For any other view you might want to protect, you need to add some checks. The @login_required decorator on your view is one way to do that, for regular function type views. Since you are dealing with Class-Based-Views, look at the Django docs here:

    https://docs.djangoproject.com/en/1.8/topics/class-based-views/intro/#mixins-that-wrap-as-view

    Another option for checking login status is to use a middleware class. That's what I'm doing in my current system since almost everything on our site requires that the user be logged in. So, in the middleware class, we check to see if request.user.is_anonymous. If they are, then there's just a small subset of pages they can access, and if they aren't accessing those pages, we redirect them to login. The middleware runs before any view so that covers the whole site.

    Ok, so now that I understand you want to actually log the user in via an ajax request, and not just check their access.. and you want control over what response comes back, then what I suggest is implementing your own view for the login service. Something like:

    class LoginView(View):
        def get(self, request, *args, **kwargs):
            user = auth.authenticate(
                username=request.GET.get('username'),
                password=request.GET.get('password'))
    
            # return whatever you want on failure
            if not user or not user.is_active:
                return HttpResponse(status=500)
    
            auth.login(request, user)
            return HttpResponse('OK')
    

    I did not test this code but it should be pretty close.