135

What is the proper way to structure a RESTful resource for resetting a password?

This resource is meant to be a password resetter for someone who has lost or forgotten their password. It invalidates their old password and e-mails them a password.

The two options that I have are:

POST /reset_password/{user_name}

or...

POST /reset_password
   -Username passed through request body

I'm pretty sure the request should be a POST. I'm less confident that I have selected an appropriate name. And I'm not sure if the user_name should be passed through the URL or the request body.

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
Chris Dutrow
  • 48,402
  • 65
  • 188
  • 258

10 Answers10

136

Unauthenticated users

We do a PUT request on a api/v1/account/password endpoint and require a parameter with the corresponding account email to identify the account for which the user wants to reset (update) the password:

PUT : /api/v1/account/password?email={email@example.com}

Note: As @DougDomeny mentioned in his comment passing the email as a query string in the url is a security risk. GET parameters are not exposed when using https (and you should always use a proper https connection for such requests) but there are other security risks involved. You can read more on this topic in this blog post here.

Passing the email in the request body would be a more secure alternative to passing it as a GET param:

PUT : /api/v1/account/password

Request body:

{
    "email": "email@example.com"
}

The response has a 202 accepted response meaning:

The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually be acted upon, as it might be disallowed when processing actually takes place. There is no facility for re-sending a status code from an asynchronous operation such as this.

The user will receive an email at email@example.com and processing the update request depends on actions taken with the link from the email.

https://example.com/password-reset?token=1234567890

Opening the link from this email will direct to a reset password form on the front end application that uses the reset password token from the link as input for a hidden input field (the token is part of the link as a query string). Another input field allows the user to set a new password. A second input to confirm the new password will be used for validation on front-end (to prevent typos).

Note: In the email we could also mention that in case the user didn't initialize any password reset he/she can ignore the email and keep using the application normally with his/her current password

When the form is submitted with the new password and the token as inputs the reset password process will take place. The form data will be sent with a PUT request again but this time including the token and we will replace the resource password with a new value:

PUT : /api/v1/account/password

Request body :

{
    "token":"1234567890",
    "new":"password"
}

The response will be a 204 no content response

The server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated with the requested variant.

Authenticated users

For authenticated users that want to change their password the PUT request can be performed immediately without the email (the account for which we are updating the password is known to the server). In such case the form will submit two fields:

PUT : /api/v1/account/password

Request body:

{
    "old":"password",
    "new":"password"
}
Wilt
  • 41,477
  • 12
  • 152
  • 203
  • In your first paragraph you say PUT but the example below says DELETE. Which is accurate? – jpierson Dec 29 '15 at 04:09
  • This does expose the email address on the URL, which would be more secure a JSON data. – Doug Domeny Apr 12 '16 at 16:28
  • @DougDomeny Yes, sending the email as json data would probably be better. I added this to the answer as an alternative more secure option, otherwise the solution can be the same. – Wilt Apr 13 '16 at 10:42
  • 2
    @Wilt : Wouldn't this be a PATCH operation ? PUT requires to send the complete resource – j10 Jul 17 '17 at 10:08
  • 2
    @jitenshah Good point. When writing this I thought PUT would be better, but I don't remember exactly why. I agree with your reasoning that patch might be more suitable for this case. – Wilt Jul 19 '17 at 06:26
  • @Wilt : I have a similar question here. Can you take a look - https://stackoverflow.com/questions/45140940/rest-api-endpoint-for-changing-email-with-multi-step-procedure-and-changing-pass It just there are multiple attributes that can be changed – j10 Jul 21 '17 at 09:35
  • @Wilt, in your answer for resetting the password you suggest providing a link in the email that contains the token as URL param. Any concerns w/providing token as a URL param? I ask because I was thinking to provide a button in email that when clicked would generate request to server but I was concerned about sending Token as URL param because of security ramifications (e.g. params aren't encrypted). Just not sure how to send back Token any other way than as URL param from email. Thoughts? Security? How to send token back in body using "email button approach"? – user2101068 Feb 25 '19 at 17:42
  • @user2101068 Adding a token to a link inside an email is common practice for functionality like password reset, account recovery, etc. Many webservices use such a solution. The token should be randomized and have a certain minimum length (strength) and to increase security it is recommended to expire them after a reasonable amount of time (i.e. one hour) meaning a new reset password link (with a new token) has to be requested. The token can be used only once and will also be invalidated after use. The link provided should be requested over a secure connection (should be an https link). – Wilt Feb 27 '19 at 11:11
  • @Wilt, thanks for the reply. I agree that it is a common practice. However, my concern (unless I'm mistaken) is that simply adding the token as a URL parameter means the token will be sent back in clear text and can be intercepted by hackers and in theory used reset the password BEFORE the user has an opportunity to use it...My understanding is that a https connection doesn't encode URL parameters. Making the token a one time use and giving it and expiration time helps but it doesn't completely eliminate the risk if the token is sent in clear text (as a url param). Thoughts? – user2101068 Feb 28 '19 at 17:31
  • @user2101068 https [does encrypt query strings](https://stackoverflow.com/a/2629241/1697459), but it comes with other security risks which are mentioned in that post. It is though the only way to get this data to a user inside an email as far as I know. If you have an alternative solution, I would be happy to hear about it. You can of course increase security by adding two step verification to this process (send another verification code to the user his/her phone before allowing to to set a new password). – Wilt Mar 01 '19 at 10:19
  • How do you find which step of password change is a request? both step has same endpoint? What if there are all three parameters in one request? Returning `422 Unprocessable Entity`? – Tooraj Jam May 30 '20 at 16:44
  • @ToorajJam Yes, exactly the parameters in the body of the request are validated on the server and depending on which parameters are present in the request a certain action is performed. The token is also uniquely bound to a certain type of verification, so trying a different action with a different token will just result in an error response. A 422 response sounds good when sending all parameters in the request. Probably the best way would be to setup some input-filter on the back-end that would fail for such an invalid request body resulting in a 422 response. – Wilt May 30 '20 at 17:29
65

UPDATE: (further to comment below)

I would go for something like this:

POST /users/:user_id/reset_password

You have a collection of users, where the single user is specified by the {user_name}. You would then specify the action to operate on, which in this case is reset_password. It is like saying "Create (POST) a new reset_password action for {user_name}".


Previous answer:

I would go for something like this:

PUT /users/:user_id/attributes/password
    -- The "current password" and the "new password" passed through the body

You'd have two collections, a users collection, and an attributes collection for each user. The user is specified by the :user_id and the attribute is specified by password. The PUT operation updates the addressed member of the collection.

Dorian
  • 22,759
  • 8
  • 120
  • 116
Daniel Vassallo
  • 337,827
  • 72
  • 505
  • 443
  • This resource is meant to reset the password for someone who has lost or forgotten their password. I clarified above. – Chris Dutrow Jun 19 '10 at 21:15
  • Oh, sorry... misunderstood... Updated my answer. – Daniel Vassallo Jun 19 '10 at 21:18
  • Yeah, that looks better than what I had, I'll have to re-factor how I'm structuring some of these resources. – Chris Dutrow Jun 22 '10 at 15:58
  • 12
    I agree with your updated (POST) solution. PUT requests should be idempotent (i.e. repeated requests should not affect the outcome). This is not the case with POST requests. – Ross McFarlane Jan 31 '11 at 14:53
  • 2
    @DanielVassallo I don't think this answer is correct. reset_password contains a verb. According to REST, resources can only consist of nouns. – Richard Knop Jan 14 '14 at 14:50
  • 23
    I would change reset_password to password_reset – Richard Knop Jan 14 '14 at 14:53
  • 1
    I am calling it `/user/{id}/password/reset`. Though, I am still thinking between the latter and `/user/password/reset`. `/user/password/reset` requires to post `first_name` & `email` to complete the request. – Gajus Mar 07 '14 at 17:41
  • 9
    Hang on guys...wouldn't this essentially allow ANYONE to reset someone's password? As, if this is for someone who forget the current password, the affected user can't be authenticated with the current password. So essentially this means this API could not accept any password at all - thus enabling anyone to reset someone's password, and if the API returns it, even get hold of any known user's password??? Or am I missing something – transient_loop Jul 08 '14 at 14:21
  • @fablife See Swards' answer below for a description of the right way to do it. If the password reset request was initiated by accident or by someone other than the user, the email can simply be ignored. – Chris Bartley Aug 13 '14 at 21:04
  • 54
    The problem with /user/{id}/password and the like is that you may not know the user's "id". You would know their "username" or "email" or "phone", but not the "id". – coolaj86 Oct 15 '14 at 03:16
  • 2
    What would be the response code - 200? Or 201, as "new resource" have been created? – Sunny Milenov Dec 02 '14 at 20:45
  • 18
    The fundamental flaw with this approach is that it assumes that you already know the user id. This will be true in some circumstances but how do you do it then when the username or user id is not known if the user only needs to specify an email for the reset. – Alappin Jan 14 '15 at 02:45
  • 1
    what about `POST /users/{username}/password/reset` ? – Manza May 22 '15 at 11:27
  • 1
    If your username is your users' unique identifier that that should work just as well but usually the userId is simpler and more URL-friendly – iuliu.net Dec 13 '18 at 15:30
  • 2
    @RichardKnop why password_reset instead of reset_password ? – Akmal Salikhov Mar 12 '19 at 09:41
  • @AkmalSalikhov because when we name endpoints we must avoid use verbs. The verb is indicated by the http verb like POST or PUT for example. – Daniel Rch. Apr 10 '21 at 19:34
21

Let's get uber-RESTful for a second. Why not use the DELETE action for the password to trigger a reset? Makes sense, doesn't it? After all, you're effectively discarding the existing password in favor of another one.

That means you'd do:

DELETE /users/{user_name}/password

Now, two big caveats:

  1. HTTP DELETE is supposed to be idempotent (a fancy word for saying "no big deal if you do it multiple times"). If you're doing the standard stuff like sending out a "Password Reset" email, then you're going to run into problems. You could work around this tagging the user/password with a boolean "Is Reset" flag. On every delete, you check this flag; if it's not set then you can reset the password and send your email. (Note that having this flag might have other uses too.)

  2. You can't use HTTP DELETE through a form, so you'll have to make an AJAX call and/or tunnel the DELETE through the POST.

Community
  • 1
  • 1
Craig Walker
  • 49,871
  • 54
  • 152
  • 212
  • 11
    Interesting idea. However I don't see `DELETE` fitting well in here. You'd be substituting the password with a randomly generated one, I guess, so `DELETE` could be misleading. I prefer the `Create (POST) new reset_password action`, where the noun (resource) you'd be acting on is the "reset_password action". This fits well for sending emails as well, since the action encapsulates all these lower-level details. `POST` is not idempotent. – Daniel Vassallo Jun 19 '10 at 22:02
  • I like the proposal. Issue 1 could be dealt with by using conditional requests, i.e. HEAD that sends ETag + DELETE and If-Match header. If someone tries to delete a password that's no longer active, he will get a 412. – whiskeysierra Jun 14 '15 at 18:31
  • 1
    I would avoid DELETE. You are updating, since the same entity/concept will get a new value. But actually, usually it's not even happening now, but only after sending the new password in a later different request (after a reset password mail) - Nowadays nobody sends a new password by mail, but a token to reset it in a new request with a given token, right? – antonio.fornie Oct 14 '15 at 15:48
  • 11
    What if the user remembers his password just after making a reset request? What about some bot trying to reset random accounts? The user should be allowed to ignore the reset email in such case (the email should say so), meaning your system shouldn't delete or update passwords by itself. – Maxime Laval Sep 23 '16 at 17:38
  • 3
    @MaximeLaval That's a very good point. Really, you're "Creating a Reset Request", which would be a POST. – Craig Walker Sep 23 '16 at 21:46
  • what if the password change was initiated by someone who is not the owner of the account? password loss? – null Sep 14 '20 at 23:21
13

Often you don't want to delete or destroy the user's existing password on the initial request, as this may have been triggered (unintentionally or intentionally) by a user that does not have access to the email. Instead, update a reset password token on the user record and send that in a link included in an email. Clicking on the link would confirm the user received the token and wished to update their password. Ideally, this would be time sensitive as well.

The RESTful action in this case would be a POST: triggering the create action on the PasswordResets controller. The action itself would update the token and send an email.

Mark Swardstrom
  • 17,217
  • 6
  • 62
  • 70
13

I'm actually looking for an answer, not meaning to provide one - but "reset_password" sounds wrong to me in a REST context because it's a verb, not a noun. Even if you say you're doing a "reset action" noun - using this justification, all verbs are nouns.

Also, it may not have occurred to someone searching for the same answer that you may be able to get the username through the security context, and not have to send it through the url or the body at all, which makes me nervous.

orbfish
  • 7,381
  • 14
  • 58
  • 75
  • 4
    Perhaps `reset-password` sounds like a verb, but you can easily reverse it (`password-reset`) to make it a noun. And if you've modeled your application using Event Sourcing or even if you just have any kind of auditing, it makes sense that you'd actually have a real entity with this name and might even allow GETs on it for users or administrators to see history (obviously masking the password text). Doesn't make me nervous at all. And as for picking up the username automatically on the server side - you can, but then how do you handle things like administration/impersonation? – Aaronaught Mar 15 '14 at 16:39
  • 1
    There is nothing wrong in using verb in REST. Just as long as it's used in appropriate places. I think this is more of a *controller* than a resorce, and `reset-password` manage to describe it's effects well. – Anders Östman Feb 04 '15 at 12:48
7

I think better idea would be:

DELETE /api/v1/account/password    - To reset the current password (in case user forget the password)
POST   /api/v1/account/password    - To create new password (if user has reset the password)
PUT    /api/v1/account/{userId}/password    - To update the password (if user knows is old password and new password)

Regarding supplying the data:

  • To reset current password

    • email should be given in body.
  • To create new password (after reset)

    • new password, activation code and emailID should be given in body.
  • To update the password (for loggedIn user)

    • old password, new password should be mentioned in body.
    • UserId in Params.
    • Auth Token in the headers.
codesnooker
  • 1,191
  • 10
  • 19
  • 3
    As commented in other answers, "DELETE /api/v1/account/password" is a bad idea, as anyone could reset anyone's password. – Maxime Dupré Feb 28 '19 at 18:00
  • We need a registered email ID to reset the password. The chances of knowing the email Id of unknown user is very grim unless we are running a site like Facebook and have tons of emails ID collected via any means. Then security policies will be defined accordingly. What's your suggestion to reset someone password? – codesnooker Mar 02 '19 at 06:54
  • @codesnooker I think it works just fine so long as it's an authorized resource. You could take a few approaches: make the DELETE only work if you have a temporary token sent via an email/messenger/text/etc or are an admin. – Josh from Qaribou Oct 22 '20 at 20:29
7

There are a few considerations to take:

Password resets are not idempotent

A password change affects the data used as credentials to perform it, which as a result could invalidate future attempts if the request is simply repeated verbatim while the stored credentials have changed. For instance, if a temporary reset token is used to allow the change, as it is customary in a forgotten password situation, that token should be expired upon successful password change, which again nullifies further attempts at replicating the request. Thus a RESTful approach to a password change seems to be a job better suited for POST than PUT.

ID or e-mail in the data load is probably redundant

Although that's not against REST and may have some special purpose, it is often unnecessary to specify an ID or email address for a password reset. Think about it, why would you provide the email address as part of the data to a request that is supposed to go through authentication one way or another? If the user is simply changing their password they need to authenticate in order to do so (via username:password, email:password, or access token provided via headers). Hence, we have access to their account from that step. If they had forgotten their password, they would've been provided with a temporary reset token (via email) that they can use specifically as credentials to perform the change. And in this case authentication via token should be enough to identify their account.

Taking all of the above into consideration here's what I believe to be the proper scheme to a RESTful password change:

Method: POST
url: /v1/account/password
Access Token (via headers): pwd_rst_token_b3xSw4hR8nKWE1d4iE2s7JawT8bCMsT1EvUQ94aI
data load: {"password": "This 1s My very New Passw0rd"}
Michael Ekoka
  • 19,050
  • 12
  • 78
  • 79
  • A statement that a placeholder requires out-of-band information isn't completly true. A special media type can describe syntax and semantics of certain elements of a request or response. It is thus possible for a media type to define that a URI contained in a certain field may define placeholder for certain data and the semantics further define that an encoded user-email or what not should be included instead of the placeholder. Clients and servers respecting that media type will still be compliant to the RESTful architecture principles. – Roman Vottner Aug 06 '18 at 15:04
  • 1
    Regarding `POST` vs. `PUT` [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4.3.4) specifies that a partial update may be achieved through overlapping data of two resources but it is questionable if something like `/v1/account/password` does really make up for a good resource actually. As `POST` is the swiss-army-kniff of the Web that can be used if none of the other methods are feasible, considering `PATCH` might also be a choice for setting the new password. – Roman Vottner Aug 06 '18 at 15:11
  • what about the URL to request the password reset when they don't know their password? – dan carter Oct 25 '18 at 22:42
2

I wouldn't have something that changes the password and send them a new one if you decide to use the /users/{id}/password method, and stick to your idea that the request is a resource of its own. ie /user-password-request/ is the resource, and is use PUT, user info should be in the body. I wouldn't change the password though, Id send an email to the user which contains a link to a page which contains a request_guid, which could be passed along with a request to POST /user/{id}/password/?request_guid=xxxxx

That would change the password, and it doesn't allow someone to hose a user by requesting a password change.

Plus the initial PUT could fail if there is an outstanding request.

bpeikes
  • 3,495
  • 9
  • 42
  • 80
0

We Update logged user Password PUT /v1/users/password - identify the user id using AccessToken.

It is not secure to exchange user id. The Restful API must identify the user using AccessToken received in HTTP header.

Example in spring-boot

@putMapping(value="/v1/users/password")
public ResponseEntity<String> updatePassword(@RequestHeader(value="Authorization") String token){
/* find User Using token */
/* Update Password*?
/* Return 200 */
}
Dapper Dan
  • 932
  • 11
  • 23
0

If you are concerned about security and prefer not to expose the user's identifier (e.g., user_name) in the URL, you could consider an alternative approach:

Use a unique token to identify the password reset request, rather than the user's identifier. This token should be generated and stored securely on the server when the password reset request is initiated.

Send an email to the user with a link that includes the token as a URL parameter, for example:

https://example.com/reset_password?token=abc123

When the user clicks the link, the client sends a GET request to the server with the token in the URL parameter.

The server validates the token and returns a page where the user can enter a new password.

When the user submits the new password, the client sends a POST request to the server with the new password and the token in the request body.

The server validates the token and updates the user's password.

This approach has the advantage of not exposing the user's identifier in the URL, which can be useful for security reasons. However, it requires additional steps to generate and store a secure token on the server, and to validate the token on the server when the user submits the new password.

rameshg
  • 51
  • 1
  • 2