36

Suppose I have a REST API that is also used to set/reset passwords. Let's also suppose that this works over a HTTPS connections. Is there any good reason not to put that password in the call path, let's also say I will encode it in BASE64?

An example would be to reset a password like this:

http://www.example.com/user/joe/resetpassword/OLDPASSWD/NEWPASSWD

I understand BASE64 is no encryption, but I only want to protect the password to shoulder surfing in this case.

7
  • 61
    Are you suggesting side effects on GET? That is a protocol violation right there. Nov 29, 2015 at 19:41
  • 27
    This is not really REST because resetpassword/OLDPASSWD/NEWPASSWD is not a resource. It's an invocation of a process. You don't need to stuff everything into a URL.
    – usr
    Nov 29, 2015 at 23:31
  • 5
    @Esben: who said it's a GET? The OP never said that.
    – dagnelies
    Dec 1, 2015 at 9:02
  • 3
    True, he didn't in the question. But his comment to Netch's answer says "Guess I'll have to use POST after all", so we can assume that he originally intended/asked about GET. Which, as Esben points out is A Bad Thing. GET should read only. Dec 1, 2015 at 13:25
  • 4
    This insightful article explaining many pitfalls of password reset mechanisms could possibly help understand the case better.
    – 9000
    Dec 1, 2015 at 14:30

6 Answers 6

81

A good server logs all requests sent to it, including URLs (often, without variable part after '?'), source IP, execution time... Do you really want this log (potentially read by a wide group of admins) to contain critically secure info as passwords? Base64 isn't a stopper against them.

6
  • 42
    This is not the biggest reason for using POST. It's a security reason. But as Esben allready note din the comments, changing state with a GET is a violation of such a Rest service
    – Pinoniq
    Nov 29, 2015 at 23:07
  • 2
    @BartFriederichs: and browser history would remember the URL. And trying out a bunch of password anonymously by making a web page that has a link for all the passwords you want to try, and letting Googlebot do the actual requests... Nov 30, 2015 at 9:30
  • 2
    "But as Esben allready note din the comments, changing state with a GET is a violation of such a Rest service" I noticed that comment, too, but I don't see where anyone's saying that this was a GET request. You can embed information in a URI and still POST it, after all. It's not really RESTful, though, since the URI isn't actually naming a resource. Nov 30, 2015 at 19:20
  • Hi, good answer, but I disagree with the "without variable part after '?'" ...there are many many which store the full URL !!!
    – dagnelies
    Dec 1, 2015 at 9:14
  • 2
    "changing state with a GET is a violation of such a Rest service" -- Let's not get too caught up in REST. Changing state with a GET is a violation of HTTP.
    – Isvara
    Dec 2, 2015 at 7:27
76

What you are proposing is neither secure nor RESTful.

@Netch has already mentioned the issue with logs, but there is also another issue in that you are showing passwords being sent by HTTP, making it trivial to capture passwords with any sort of wire sniffer or man-in-the-middle attack.

When you do a GET request using REST, the different elements in the URL represent more fine grained elements. Your URL reads like you are returning a NEWPASSWD part of an OLDPASSWD that's part of a resetpassword. That does not make any sort of semantic sense. GETs should not be used to save data.

You should be doing something like this:

POST https://www.example.com/user/joe/resetpassword/
{oldpasswd:[data], newpasswd:[data]}

POST because you are writing data, and https because you don't want it sniffed.

(This is really the low-bar security. The absolute minimum you should do.)

12
  • 2
    Wouldn't this mean hashing the passwords on the client-side? Is this recommended? Nov 29, 2015 at 22:24
  • 1
    Note: this will not allow enforcing password (length, etc.) requirements. That may not be an issue in your case, though it is a frequent security practice, and sometime required by some entities. Nov 29, 2015 at 22:45
  • 8
    You're not creating a new record but updating an existing record (usually), so this should be PUT rather than POST.
    – user76821
    Nov 30, 2015 at 6:04
  • 4
    This is not very RESTful. resetpassword is not a resource let alone sub-resource. However, /user/joe/password is a little better but not optimal.
    – whirlwin
    Nov 30, 2015 at 12:13
  • 12
    @CamilStaps No, you cannot use PUT, because PUT is idempotent. But when the password has been changed from secret to supersecret sucessfully, then the same request will fail the second time, so POST is correct here. Of course, as @whirlwin said, this resource is not well named.
    – Residuum
    Nov 30, 2015 at 16:35
64

The proposed scheme has issues in several areas.

Security

URL paths are frequently logged; putting unhashed passwords in the path is poor practice.

HTTP

Authentication/authorization information should appear in the Authorization header. Or potentially, for browser-based stuff, the Cookie header.

REST

Verbs such as resetpassword in your URL are generally a clear sign of a non-representational state transfer paradigm. A URL should represent a resource. What does it mean to GET resetpassword? Or DELETE?

API

This scheme requires always knowing the previous password. You will probably want to allow for more cases; e.g. the password is lost.


You could use Basic or Digest authentication, which is are well understood schemes.

PUT /user/joe/password HTTP/1.0
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Content-Type: text/plain
Host: www.example.com

NEWPASSWD

It doesn't put ultra-sensitive information in the path, and it follows HTTP and REST conventions.

If you needed to allow for some other mode of authorization (e.g. some token sent through a verified channel to reset the password), you can simply use a different Authorization header without having to change anything else.

0
5

The problem is to avoid plain text passwords in your requests. There are two options to fulfill the restful webservice requirements.

1. Client side hashing

  • I'll guess you are storing your passwords like e. g. hash(password+salt)
  • You can hash the new password with a salt on the client side
  • That means: Create a new salt on the client side, create a hash e. g. hash(newPassword+newSalt)
  • Send the new created hash plus the salt to your restful webservice
  • Send the old password also as hash(oldPassword+oldSalt)

2. Encryption

  • Create a "one time key" (otk) resource for a user like /otk/john
  • This resource returns an secure random unique one time key, e. g. kbDlJbmNmQ0Y0SmRHZC9GaWtRMW0ycVJpYzhMcVNZTWlMUXN6ZWxLdTZESFRs and a unique ID e. g. 95648915125
  • Your restful webservice has to store this random otk for the next secure communication with the ID 95648915125
  • Encrypt your new and old password with the otk e. g. AES (for security reasons you should use two separate otks for the old and new password)
  • Send the encrypted passwords to your change password resource with the ID 95648915125
  • One otk and ID combination is allowed to work once only, so you have to deleted that combination after chaning the password
  • Possible better option: Send the current/old password by Basic-Auth.

Note: HTTPS is required for both options!

2
  • 1
    Why would I double-encrypt (your encryption scheme with the OTK, and HTTPS) the password exchange? What's the attack vector here that is not covered by HTTPS? Dec 1, 2015 at 14:07
  • 1
    The only purpose is to avoid possible server logs. A HTTP request body (for example with a plaintext new password) can also be logged although HTTPS is used. Other possible attack is the usage of a self-signed certificate which is especially used for internal purposes.
    – maz258
    Dec 1, 2015 at 14:13
4

Apart from security, the problem with this is that it isn't a very RESTful approach.

OLDPASSWD and NEWPASSWD don't stand for anything in your resource hierarchy and even worse, the operation isn't idempotent.

So you can only use POST as your verb, and you should not include the two passwords in your resource path.

4
  • 1
    You're not creating a new record but updating an existing record (usually), so this should be PUT rather than POST.
    – user76821
    Nov 30, 2015 at 6:05
  • 2
    @CamilStaps If it was just setting the password, it could be. But since presumably the old password needs to be verified too, it makes the operation non-idempotent, and thus PUT is disqualified as a verb. It could be rejigged to work with PUT but in its current form it doesn't.
    – biziclop
    Nov 30, 2015 at 9:57
  • OLDPASSWD is authentication info and shouldn't be in the URL at all.
    – user76821
    Nov 30, 2015 at 9:58
  • Not necessarily, it is common practice to explicitly ask for the old password on top of authentication.
    – biziclop
    Nov 30, 2015 at 10:02
2

What are the features of a password-reset operation?

  1. It change something.
  2. There is a value that it is set to.
  3. Only some people are allowed to do it (the user, an admin, or either, perhaps with different rules as to how either can do so).

Point 1 here means you cannot use GET, you must either POST something representing the password-change operation to a URI representing a resource that handles password changes, or PUT something representing the new password to a URI representing the password or representing something (e.g. the user) of which that password is a feature.

Generally we'd POST, not least because it can be awkward PUTting something we can't later GET and of course we can't GET the password.

Point 2 therefore will be data representing the new password, in what is POSTed.

Point 3 means we'll need to authorise the request, which means that if the user is the current user we'll need the current password to be proven to us (though not necessarily receive the current password, if e.g. a hash-based challenge was used to prove knowledge of it without sending it).

The URI should therefore be something like <http://example.net/changeCurrentUserPassword> or <http://example.net/users/joe/changePassword>.

We might decide that we want to receive the current password in the POST data as well as in the general authorisation mechanism being used.

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