3

Assuming I have a RESTful service and it always responds in this format:

{
    "error": { 
        "code": ...,
        "message": ...
    }
}

or

{
    "data": ...
}

and there is a method like POST /users/me/change-password.
What would be the correct and RESTful behavior:

  1. Business logic / validation errors are data

    • Password has been changed (200, empty error and data)
    • User is not authenticated (401, error = 1, empty data}
    • Password policy: password has already been used (200, empty error, data = 1)
    • Password policy: password doesn't match requirements (200, empty error, data = 2)
    • SMS-OTP confirmation required (200, empty error, data = 3)
    • Incorrect SMS-OTP (200, empty error, data = 4)
    • The user has disabled password changing (200, empty error, data = 5)

    It seems correct since errors like "SMS-OTP confirmation required" are not even client errors. A client did nothing wrong, and expects an "OK" response.

  2. Business logic / validation results are errors

    • Password has been changed (200, empty error and data)
    • User is not authenticated (401, error = 1, empty data}
    • Password policy: password has already been used (400, error = 2, empty data)
    • Password policy: password doesn't match requirements (400, error = 3, empty data)
    • SMS-OTP confirmation required (400, error = 4, empty data)
    • Incorrect SMS-OTP (400, error = 5, empty data)
    • The user has disabled password changing (400, error = 6, empty data)

    It seems correct since 200 OK status suggests that password has been changed succesfully, whatever written in response. However, some clients like C# WebClient do not even return non-OK responses, they just throw exceptions.

The same questions arise in terms of all authentication requests. For example, is "username is already occupied" for "sign-in" an error or data.

  • Will you create a consumer of your API? Will it use third-party REST clients? – Basilevs Dec 15 '16 at 5:42
  • @Basilevs No, iOS and Android clients will be written by other outsource developers according to my specification. I don't know what libraries or technologies they will use in advance. – Yeldar Kurmangaliyev Dec 15 '16 at 5:43
  • 1
    IMO, HTTP status alone should be enough to detect exceptional situation. There are tons of libraries, which are much easier to use if API complies. Unfortunately I have no expertise over your target platforms, so let's wait for some user of REST clients widely used there comes to confirm this. – Basilevs Dec 15 '16 at 5:49
  • Consider finding even more specific status codes for your cases, they should always be as specific as possible. – Basilevs Dec 15 '16 at 5:51
  • 1
    The first approach just say: "you successfully failed changing your password". I found it to be confusing (from the perspective if the client). Informing the error code through the http status is enough. – Laiv Dec 15 '16 at 6:29
1

REST gurus will tell you that every possible error has an HTTP error code which maps to it.

However, before we get caught up in that madness, we should remember that throwing exceptions shouldn't be your first choice for returning data to the user.

In this case it seems to me that if you refactor your 'change password' method to return an object, or even just a bool half of those exceptions disappear.

I think this is the right choice for situations where you have multiple errors, such as password doesn't meet strong password criteria x, y, and z.

keep the exceptions for exceptional stuff ie. user not found or access denied.

This calls out the possible responses that the consumer should handle specifically and lets them leave (90% of) the exceptions to their default 'show error to user' handler.


Note: This is different from returning the exception as a json object with a 200 response code. Which should be avoided.


eg.

public class PasswordChangeResult
{
    public bool PasswordChanged;
    public List<string> ValidationErrors;
}

OR an HTTP error

is always returned.

  • good points. I would also point out that exceptions thrown to the client can expose waaaaayyyy too much information. So besides not being user friendly they can aid attackers. Ideally error paths are well defined, handled, and return an appropriate status code indicating such. – Adrian Dec 15 '16 at 9:35
  • If the boolean is for checking whether the process ended successfully or not, then It's the same scenario than "successfully failed". Why do you want clients looking for codes/bool in the body response? Would not be easier to send a 409 and one message error. The client neither need the password back from the server, it already have it. – Laiv Dec 15 '16 at 20:30
  • then you would be throwing exceptions for unexceptional things. dont force the client to use exception handling for control flow – Ewan Dec 15 '16 at 21:57
  • same goes for 200 empty response. did I update the password or call the wrong endpoint?. – Ewan Dec 15 '16 at 21:58
  • If I want to respond an empty body, I will use 204. And I expect client understanding that 204 means: Ok all right, no body in return. – Laiv Dec 16 '16 at 20:55
-1

Business logic / validation results are errors

This is the correct response for REST.

REST is state transfer. The client does something to a resource and transfers the new state to the server.

You should change the resource from /users/me/change-password to /users/me/password and the HTTP verb from POST to PUT.

The client is saying "Here is the new state of the users password, it is now this" and sends that message to the server. The server can either accept that state update, or reject it.

If the server rejects that update it should return an error. The client needs to know that the state transfer did not take place. But try not to think of HTTP 4xx response codes as having different meanings depending on your specific domain. They relate to the state transfer part of the communication, not the domain. Most deal with the mechanics of that state transfer (is your message syntactically correct, was there a network error, are you authenticated etc)

For example 400 means bad request from the point of view of the formatting of the HTTP request. If the request to the server was a perfectly valid HTTP request but the server is rejecting it for some business domain reason, it shouldn't reply 400

Often the correct response is 403 Forbidden when a business rule fails and the server rejects the state update request. If you think of it this way, the server is saying to the client "Everything is syntactically fine with this request, but I will now allow this update because of this reason", basically forbidding the update.

You might have

403 Forbidden Password has already been used

and

403 Forbidden Password doesn't match requirements

Both are valid. The 4xx are not designed to tell the client the specific domain reason that the server didn't do something. That is what the response body is for, and a client should understand both the general HTTP response codes and the specific messages of the response bodies

  • HTTP Status code - rfc2616 - Status code 403 - "The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity." – linuxunil Dec 15 '16 at 17:45
  • a status code of 403 is about authorization. Not about errors. A status code 400 Bad Request is more appropriate. – linuxunil Dec 15 '16 at 17:46
  • Notice the "authorization will not help". If it was possible for the client to authorise the state update the client should return 401 Unauthorized with details of how the client can authorise itself, or if the client has authorised itself but lacks the specific permissions, it should return that. 403 is for when authorisation won't do anything, the server is just saying "Nope, won't do that" – Cormac Mulhall Dec 15 '16 at 17:51
  • 400 Bad Request means that the actual formatting of the HTTP request is wrong and the server cannot understand it. "The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications." 400 is for when the server doesn't understand what the client is asking it to do, rather that it won't do it. That is important difference because a 400 often means that there is a bug in the client, it is producing invalid formatted HTTP requests. – Cormac Mulhall Dec 15 '16 at 17:54
  • 4XX codes is the family of codes of Client Errors. In this case a 400 is a generic bad request. The others 4XX codes are especializations of this generic. – linuxunil Dec 15 '16 at 17:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.