RESTful password reset
Asked Answered
N

10

140

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.

Necrosis answered 19/6, 2010 at 21:6 Comment(0)
Q
66

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.

Qianaqibla answered 19/6, 2010 at 21:11 Comment(16)
This resource is meant to reset the password for someone who has lost or forgotten their password. I clarified above.Necrosis
Oh, sorry... misunderstood... Updated my answer.Qianaqibla
Yeah, that looks better than what I had, I'll have to re-factor how I'm structuring some of these resources.Necrosis
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.Aniakudo
@DanielVassallo I don't think this answer is correct. reset_password contains a verb. According to REST, resources can only consist of nouns.Ankney
I would change reset_password to password_resetAnkney
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.Clyde
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 somethingDissuade
@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.Cuspidation
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".Benton
What would be the response code - 200? Or 201, as "new resource" have been created?Crate
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.Kenn
what about POST /users/{username}/password/reset ?Creepy
If your username is your users' unique identifier that that should work just as well but usually the userId is simpler and more URL-friendlyToodleoo
@RichardKnop why password_reset instead of reset_password ?Tipton
@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.Teenateenage
L
141

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 protected]}

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 protected]"
}

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 protected] 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"
}
Linders answered 28/10, 2015 at 11:13 Comment(12)
In your first paragraph you say PUT but the example below says DELETE. Which is accurate?Grapevine
This does expose the email address on the URL, which would be more secure a JSON data.Shetrit
@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.Linders
@Linders : Wouldn't this be a PATCH operation ? PUT requires to send the complete resourceDarling
@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.Linders
@Linders : I have a similar question here. Can you take a look - #45141440 It just there are multiple attributes that can be changedDarling
@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"?Leak
@Leak 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).Linders
@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?Leak
@Leak https does encrypt query strings, 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).Linders
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?Media
@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.Linders
Q
66

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.

Qianaqibla answered 19/6, 2010 at 21:11 Comment(16)
This resource is meant to reset the password for someone who has lost or forgotten their password. I clarified above.Necrosis
Oh, sorry... misunderstood... Updated my answer.Qianaqibla
Yeah, that looks better than what I had, I'll have to re-factor how I'm structuring some of these resources.Necrosis
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.Aniakudo
@DanielVassallo I don't think this answer is correct. reset_password contains a verb. According to REST, resources can only consist of nouns.Ankney
I would change reset_password to password_resetAnkney
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.Clyde
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 somethingDissuade
@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.Cuspidation
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".Benton
What would be the response code - 200? Or 201, as "new resource" have been created?Crate
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.Kenn
what about POST /users/{username}/password/reset ?Creepy
If your username is your users' unique identifier that that should work just as well but usually the userId is simpler and more URL-friendlyToodleoo
@RichardKnop why password_reset instead of reset_password ?Tipton
@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.Teenateenage
P
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.

Prognosticate answered 19/6, 2010 at 21:34 Comment(6)
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.Qianaqibla
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.Guidry
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?Myrilla
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.Particia
@MaximeLaval That's a very good point. Really, you're "Creating a Reset Request", which would be a POST.Prognosticate
what if the password change was initiated by someone who is not the owner of the account? password loss?Shere
P
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.

Pleo answered 28/1, 2013 at 20:59 Comment(0)
J
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.

Jens answered 6/1, 2014 at 20:24 Comment(2)
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?Ichnology
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.Laddie
B
8

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"}
Boettcher answered 23/6, 2018 at 13:11 Comment(3)
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.Stem
Regarding POST vs. PUT RFC 7231 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.Stem
what about the URL to request the password reset when they don't know their password?Florentinoflorenza
C
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.
Coprology answered 24/11, 2017 at 14:25 Comment(3)
As commented in other answers, "DELETE /api/v1/account/password" is a bad idea, as anyone could reset anyone's password.Kalil
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?Coprology
@Coprology 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.Buzz
R
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.

Reedreedbird answered 3/9, 2014 at 2:51 Comment(0)
J
1

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.

Joab answered 20/2, 2023 at 16:45 Comment(0)
S
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 */
}
Sexuality answered 28/2, 2018 at 15:38 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.