Validate/Change Password via REST API

16,929

Solution 1

I'll start by pointing out that authentication is often handled outside of a REST model. When a user provides their credentials, they are not providing a REpresentation of their account object's STate (REST); nor is the response they receive such a representation. Because the user account resource state does not include both 'current' and 'new' passwords, the provision of both a 'current' and a 'new' password in a request can never truly fit under the REST model, but professionals often describe a 'continuum' of RESTfulness, with some APIs being completely RESTful and others falling between RPC (Remote Procedure Call) and REST.

It's not uncommon to have a RESTful component of an API that handles the serving of data models, and a more RPC component of an API that handles user accounts. You get to decide between the two. If your project includes super users that manage multiple user accounts, I would suggest trying to shoe-horn that into a REST API. If each user manages only their own account, I would suggest RPC.

If you've decided to use REST for account management, then you must choose an appropriate HTTP method (GET, POST, DELETE, HEADERS, etc). Clearly you require a method that will effect a change on the server (POST, PUT, DELETE, etc). In contrast to user orbfish's conclusion above, I'm going to say that PUT would be an appropriate method, under certain restrictions.

From RFC 2616, which formally defines our HTTP methods:

"Methods can also have the property of 'idempotence' in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. The methods GET, HEAD, PUT and DELETE share this property. Also, the methods OPTIONS and TRACE SHOULD NOT have side effects, and so are inherently idempotent. "

Idempotency here means that if we make the same request n times in a row, the state of the server under the effects of the nth request will be the same as the state of the server under the effects of the first request. User orbfish correctly notes that if we make the request:

PUT /users/:id/account {current-password: 'a', new-password: 'b'}

and repeat it:

PUT /users/:id/account {current-password: 'a', new-password: 'b'}

that our first request should receive a response indicating success and our second request should receive a response indicating failure. However, the idempotency of PUT only requires that the state of the server is the same following both requests. And it is: after the first request the user's password is 'b' and after the second request the user's password is 'b'.

I mentioned restrictions above. You might want to lock a user out after m attempts to change a password unsuccessfully; this would provide security against brute-force password attacks. However, this would break the idempotency of the request: send a valid password request one time and you change your password, send it m more times and the server locks you out.

By specifying the PUT method, you are telling all clients that it's safe to send a request as many times as needed. If I as a client send a PUT request and our connection is interrupted such that I don't receive your response, I know that it is safe to send my PUT through again because it is idempotent: idempotency means that if you receive both requests it will be the same to your server as just receiving one. But if you're going to lock me out for an unsuccessful request, then it isn't safe to send a second request until I know whether you have received the first.

For this reason, you might consider PATCH or POST. I would suggest using PATCH. Whereas POST is described as either appending a new resource to a list or appending data to an existing resource, PATCH is described as a 'partial update' on a resource at a known URI. And unlike PUT, PATCH need not be idempotent.

Solution 2

I don't like /check_password or /validate because they're verbs; your first "update user" is better REST.

You can add the currentPassword to your User object as an unpersisted field, or as part of the Authentication header (username:password).

I would definitely change this from a PUT to a POST, though, because the same call with the same currentPassword cannot succeed twice (PUT is idempotent).

Solution 3

You are changing a property of the user resource (i.e., the password). If you use HTTP Basic for your authorization, you are already providing the current password, so there is no need to repeat it. I would simply PUT the entire user resource with the new password. Example:

PUT /users/fiddlerpianist HTTP/1.1
Content-Type: application/json
Authorization: Basic ZmlkZGxlcnBpYW5pc3Q6bXlub3Rzb2F3ZXNvbWVvbGRwYXNzd29yZA==

{
    "password": "My awesome new password that no one will ever be able to guess!"
}

The other advantage of doing it this way is that you don't necessarily need to provide the old password, as long as you are a credentialed user that has access rights to modify the user resource. Maybe you're a customer support specialist who is never supposed to ask for the customer's old password but they are requesting a password change over the phone (after they've proven to you their identity and you've proven your identity to the system).

You want to avoid using a non-idempotent request in this case (such as PUT or PATCH), as that can lead to responses whose outcomes are uncertain (suppose the server returns a 500 for a non-idempotent request... you as the client have no idea what state the server left your resource in).

EDITED TO ADD: Note that, in a RESTful app, there is no concept of being "logged in." The communication from the client to the server is entirely stateless (it's the payload and method that communicates the state). Also, there really doesn't need to be a concept of validation the way you describe it, as a request to change the state of a resource can either be met with a 200 OK (if valid) or a 400 Bad Request (if invalid).

Solution 4

Another option is to create surrogate resources on user. If you're using HATEOAS you could link down to user/x/pwdchange from the user resource. And I want to clarify pwdchange is conceived as a noun/resource, not as a verb:

GET /user/jsmith/pwdchange     List of password change requests (history)
POST /user/jsmith/pwdchange    Create password change request, return id=1
GET /user/jsmith/pwdchange/1   Get password change resource, which would
                               include the outcome (success, failure, etc)

So, in short, I'm creating a resource named 'pwdchange' that is fully compliant with a REST view of the problem domain.

Solution 5

You might think about why you need to validate the current password as soon as it's entered. I've not seen a site do that. Second, it's perfectly fine to have a service that just validates something. Its called being practical verse beating yourself up trying to be RESTFul

Share:
16,929
Kareem
Author by

Kareem

Updated on July 08, 2022

Comments

  • Kareem
    Kareem almost 2 years

    I want to change a user password via a REST API. This is not a forgotten or reset password function, but a logged in user wanting to change their password.

    The form requires the current password, the new password, and a confirmation for the new password. However, I want to validate each form field as the user fills it out. This is trivial for newPassword and confirmNewPassword (client side), but not for currentPassword. Currently performing update to the User object via PUT /users/:id. If a password parameter is passed, I check for the currentPassword parameter and ensure it is correct prior to saving. However, for validation, I'm unsure of the best approach.

    I also have a POST /users/validate - not sure if this is best either. This validates a User object for both create and update, but only validates fields that belong to the User object (email, username, password). currentPassword isn't one of these. Wondering how to handle this. Some things I've considered:

    POST /users/check_password, POST /users/validate (adding in validation for currentPassword if that parameter is passed, and check that currentPassword matches the users existing password) and POST /users/:id/validate (separate validation for existing user, requiring currentPassword).

    Any thoughts or advice would be greatly appreciated. My first application that only exposes functionality via REST API.