5

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)?

Community
  • 1
  • 1
SilentDev
  • 20,997
  • 28
  • 111
  • 214
  • Something is wrong, if a user submits a wrong or incorrect username/password, you should return 404 or 400, not 204. – levi Sep 23 '15 at 04:39
  • @levi Exactly. I wouldn't expect 400 because the syntax is fine and the server can understand the username and password being sent, but I'd at least expect a 404 rather than a 200 or 204. – SilentDev Sep 24 '15 at 02:18
  • Just be careful, there is a new RFC that obsoletes the one you cite. Take a look at 7231. – Pablo Sep 27 '15 at 16:31
  • @PabloPalácios Hm, which document are you referring to when you mention to take a look at 7231? I went to this site: http://www.w3.org/Protocols/ and RFC 7231 leads to this: http://tools.ietf.org/html/rfc7231 (it has to do with Semantics and Content). Where does it mention how to handle POST requests? – SilentDev Sep 27 '15 at 21:35
  • As said, just be careful. Perhaps there is not significance changes about POST method in the new RFC. But doesn't cost to take a look at that first and update your question. – Pablo Sep 27 '15 at 21:50

3 Answers3

8

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.

spectras
  • 13,105
  • 2
  • 31
  • 53
  • Do you think using 422 (unprocessable entity) as an option? The server cannot process your log-in request based on the entity (credentials) you have submitted. – Michael Tsang Apr 13 '17 at 05:01
  • @MichaelTsang> Response code [422](https://tools.ietf.org/html/rfc4918), right? From the context of the RFC, its intent *seems* to be signalling an error with semantics. It gives the example of a correctly formed XML file, the content of which makes no sense. I guess 422 could be an appropriate choice for a login request that misses a field for instance, but for an incorrect password it is dubious. After all, the server *did* process the login request to come to the conclusion that the password is incorrect. – spectras Apr 13 '17 at 11:22
  • Here it says 401 is correct : http://stackoverflow.com/questions/32752578/whats-the-appropriate-http-status-code-to-return-if-a-user-tries-logging-in-wit/32752617 – RoaflinSabos May 10 '17 at 16:32
  • 1
    @RoaflinSabos> the reason it gives is incorrect, and is specifically addressed in this answer: it confuses transport and content. – spectras May 11 '17 at 08:21
5

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?

Fernando Pinheiro
  • 1,796
  • 2
  • 17
  • 21
  • but according to RFC 7235: https://tools.ietf.org/html/rfc7235#section-3.1 it says " If the request included authentication credentials, then the 401 response indicates that authorization has been refused for those credentials. The user agent MAY repeat the request with a new or replaced Authorization header field." So wouldn't 401 be the correct response if the request included authentication credentials which have been refused? – SilentDev Sep 28 '15 at 17:44
  • 1
    No, because we're talking about two different kinds of requests. The login request cannot be considered as any other request that fetches data, for example, because it is somehow the main part of the authentication process. For instance, a login request does not contain the authentication credentials in it's header, but in it's body. So you cannot unauthorize a login request, because it will always be, by it's nature, a request accessible for everyone. So what the login response does is to tell whether the credentials are ok or not ok, rather than unauthorize the request. – Fernando Pinheiro Sep 28 '15 at 17:48
  • 1
    @user2719875 returning 401 would imply the user must login before he is allowed to access the login form. – spectras Oct 01 '15 at 18:49
  • @spectras, thank you. that's exactly what I meant, but in short words :) – Fernando Pinheiro Oct 01 '15 at 18:50
2

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.

Community
  • 1
  • 1
Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148