Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework - Why is a 200 status code returned when trying to login a user using incorrect credentials?

This is my URLs.py:

url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),

I have a form on my homepage where users can type a username and password. When the submit button is clicked, AngularJS sends a POST request to "api-auth/login/" with the user object (username and password):

$http.post("/api-auth/login/", self.loginuser)
    .error(function(data, status, headers, config) {
        console.log(data);
     });

When a user submits an incorrect username and password (username and password which either do not exist or do not match), Django Rest Framework returns a 200 OK rather than a 204 No Content, 404 or 401 Unauthorized (on this post, it says 401 is the correct status code to return: What's the appropriate HTTP status code to return if a user tries logging in with an incorrect username / password, but correct format?).

According to here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html in section 9.5 POST, it says "In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result."

I handle errors and log the data if data exists (I did console.log(data) in JS), but no data is logged, which means (from my understanding) no data is sent / the response does not include an entity that describes the result.

So how come DjangoRestFramework returns a 200 rather than a 204 No Content (or a 404 or 401, which is what should be returned according to the other SO post I linked to)?

like image 671
SilentDev Avatar asked Sep 22 '15 23:09

SilentDev


3 Answers

Several things are mixed up in your question. First, the technical views you use and second the way you interpret answers.

1) The views

That's a quick one. The view you use by sending data to /api-auth/login/ it DRF's Login view for the browsable API. This view, which is actually the one that ships with Django's auth app (django.contrib.auth.views.login) assumes it is dealing with a user, browsing the API manually.

That is: calling it with GET builds an empty html form, sending back the form with POST will trigger form validation, which can end up either in the form being redisplayed (200 Ok with a document included), or a redirection to be sent back (302 Found with empty content).

This is why your data is empty: the server sends an HTML document, while your angular probably tried to parse some JSON object.

The form view you use is absolutely not intended to be called from a script. This can be done, but you need to handle the result accordingly, which means analyzing the returned html page to look for error messages. Messy.

You should build your own login view if you want to access it from script easily.

2) Document vs request

You are dealing with two separate levels of semantics here.

  1. the meaning of the request.
  2. the meaning of the document enclosed in the request.

HTTP error codes are meaningful in the context of the request. They happen at a lower level. For instance, returning 401 code means “valid authentication credentials are required before performing this request”.

So here, that basically would mean “you must have valid authentication credentials before I process your login request”.

It could make sense, but only in a context where you have two layers of authentication. You would need to have authentication credentials valid for the first layer before it lets your second-layer-login-request through. In this case, you could get an 401 if you try to login with the second layer while not recognized by the first.

So, how does REST fit in?

The concept of REST, when applied to HTTP, is to try to match the request-level semantics and the document-level semantics. It fits particularly well because every REST concept has a matching HTTP verb, HTTP is cacheable, client-server, ... and... stateless.

Stateless means neither HTTP nor REST have the concept of logging in. Logging in is an abstraction, which usually means we use a workflow that looks like this:

  1. we authenticate to some endpoint (login/password, challenge, oauth, whatever).
  2. the endpoint returns some authorization token
  3. we send the authorization token with every next request to the server.

But truth is, every single request has to be authorized by the server. That is, the server will always start by authorizing the request, before looking at what's inside. If this step fails, 401 is an adequate response.

Except step #1. This request does not have the authorization step. It must be processed, the login/password must be retrieved and checked, and depending on the result, the server may decide to send back an authorization token.

So, what error codes would be appropriate if it chooses not to? Well, there are several you may choose from:

  • 200 Ok. The login request was successfully handled and yielded an error message, here is a document with the result. Your script would then read the document (could be a JSON object for instance) to see if it has errors or an authorization token.
  • 204 No Content. The login request was successfully handled, but yielded nothing and certainly no authorization token. Odd choice, but correct.
  • 400 Bad request. The login request was not handled successfully.

Only sure thing is, 401 is not an option. 401 would mean you were not allowed to attempt to login. Not that the login failed.

like image 164
spectras Avatar answered Oct 05 '22 07:10

spectras


In my humble opinion, you are mixing business concepts with protocol issues here.

A login method should not return 401 (unauthorized) because it's prerequisite is that the user is (obviously) not yet authorized/authenticated. So if the request is made in a correct way (syntactically speaking), despite of the user credentials are incorrect (business concept), the response should be 200 (protocol), i.e., the request was accepted and properly processed. And of course, the response body will determine whether it was a successfull login or not.

So, after all, you are trying to log an application error when it is actually a business layer error (the user entered incorrect values according to your database). Get it?

like image 22
Fernando Pinheiro Avatar answered Oct 05 '22 06:10

Fernando Pinheiro


If you look at DRF's source you will see that it uses Django's own Login/Logout views. Therefore this is question would be more related to Django's form handling itself. Here's a question that's related to Django itself.

like image 20
Bernhard Vallant Avatar answered Oct 05 '22 07:10

Bernhard Vallant