4

I'd like to design a REST API supporting:

  • Login
  • Temporary token generation

The reason being there are a number of client-side REST libraries that would speed up development if used, as they take care of serialization, connection, etc. If not used, we would have to code these parts separately. I don't need REST for performance, there will be no load balancing or caching involved in the server side.

Login and token generation are quite common. And yet I'm having a hard time trying to figure how to do them in REST. For instance:

Login

I've read a lot about login in REST, and apparently there's no right answer here. In the end many people end up using OAuth just because it is mainstream. Actually I just need to check if the user exists and the password is correct. Any other operation in the API will be passed the user name and password for authorization purposes, so for now we are still in the stateless side.

The problem here is that I've envisioned login as a query against the users collection:

GET https://api.example.com/users?usr=username&psw=1234

The server will respond with a filtered list of a single user.

But I don't like passing the password in the query string. It does not look OK to me. The connection will be made using HTTPS, but there's an additional step of having to URL-encode all odd characters, and decode them in the server, which we wont have in a POST. And also URLs are more frequently logged than payloads.

I could also get the password in the server response:

GET https://api.example.com/users?usr=username

The returned JSON object would contain all the fields for the user (id, password, etc), so I could check the password in the client.

Which one is better? Any alternative?

Token generation

A registered user is able to generate temporary tokens. I'm struggling trying to force tokens into being a resource. They are generated on the fly, and the operation is not idempotent, as each subsequent token request will return a different token. To make things worse, this operation is stateful: there will be a temporary table in the backend where tokens will be stored for a period of time. So what would be the REST version? It could be a PUT if the client were the one generating it, but it is the server.

TL;DR This is so difficult. If I succeed in creating a REST version of this API, the client code will be shorter and clearer, as we will be using well-known libraries that have been extensively tested. But honestly it looks almost impossible to force the stateful into stateless. Maybe I should just give up and provide a straightforward stateful API? How could I explain the extra time it would take to management? They will probably argue that nowadays everything is REST.

7
  • 3
    never send the password itself, hash it (using bcrypt) and send the hash. The back end should also store the hash to compare against. Try asking on Security.SE
    – gbjbaanb
    Jan 18, 2016 at 11:09
  • 2
    it looks almost impossible to force the stateful into stateless -- It looks like you think REST implies lack of state. But nothing prevents you from saving the result of a REST call in a database, or storing cookies on the client's browser, or performing any other meaningful action involving some state. "Stateless" just means that each REST call can act independently of the others, which is always true. Jan 18, 2016 at 16:47
  • 3
    Maybe you can take some inspiration from GitHub's API. I can't say how RESTful it is, but I also don't care about such dogmatism. I only care how useful a thing is.
    – RubberDuck
    Jan 31, 2016 at 18:53
  • 4
    @gbjbaanb surely that's not true. If the client hashes the password the hash is the password. Knowing the hash allows access to the system (you just set your client to send it directly, not hash it) Jan 31, 2016 at 22:15
  • 3
    @gbjbaanb security.stackexchange.com/a/66476/17974
    – Andy
    Feb 5, 2016 at 1:31

5 Answers 5

3
+50

First of all, let me say that I agree 100% with @MvdD, that a REST API should not include login/logout semantics. However, as I'm sure you've heard/read OAuth is a headache you really don't want if you aren't "required" to have federated auth!

As other answers have pointed out, there is no one pattern to accomplish this, with several being accepted as answers in previous similar questions (mostly from SO). I'm also assuming you've looked at previous related questions and such. There is also this question (RESTful Authentication) on SO, which details 4 techniques (some more RESTful than others). If you're (seemingly?) happy to transmit the password as cleartext in HTTPS then option #1 of that answer (HTTP basic auth) would work with pretty much any HTTP library your REST tools are wrapping.


If you must...

Since there's a bounty and all (and because it's an interesting challenge), I thought about it, and (after about 1.5 cups of coffee) have had the following idea: POST to a "challenge" resource, which then redirects to a resource identifier for that challenge, (and no this is not idempotent).

What am I talking about?

Well, as pointed out here (Table 1, page 3), resource identifiers are different to resources themselves. Ergo, you can RESTful-ly POST to one URI but be causing a change (i.e. RESTful change, such as CREATE, UPDATE) in a resource not necessarily located there. Let me explain in action...

Authentication Process:

  1. Client sends POST /challenges with payload of username. This triggers the server to create necessary backend entry in DB table, allocate resources, etc.
  2. Server responds with a 303 See Other (which is just a newer version of 302 Moved temporarily), directing client to /challenges/<some random request code>.
  3. Client GET's /challenges/<some random request code> (Note, will be GET, as 303 is used instead of 302). Which returns (with a 200 OK) a JSON-encoded challenge (more on this later).
  4. Client responds to challenge, this time with POST to /challenges/<some random request code> with a hash meeting the challenge as payload.
  5. Server responds with either 403 Forbidden (if challenge failed, ergo, wrong password, etc) or a 200 OK (optionally with a JSON payload containing your session info or user profile or what-have-you).

A word on Challenges: (As promised in step #3)

The challenge given by the server is basically just some task (usually hashing, but possibly a two-way encryption/decryption, etc) given by the server to the client. The server knows (computes) the result (hence why hashes are good, since they're cheap) ahead of time, and compares that with the challenge-responce given by the client.

This can be as simple as some random text (or even static text, like your application name, etc) sent to the client in plaintext (or hash, not important) with the server having worked out the hash of that text (essentially a salt) concatenated with the (hashed, since it's in the DB - and we don't store plaintext passwords in databases) password. If the client can concatenate this salt with the password (after hashing the password locally) and then hash that concatenation, it can answer the challenge.

Example:

  • server: salt = md5("MaryHadALittleLamb") -> 8c20828418ca489f5b949f25f35abaa0 (sends to client in step #3).
  • client: hashes the password "password" ('username' is a notoriously insecure password selector) to produce 5f4dcc3b5aa765d61d8327deb882cf99, this is then concatenated with 8c20828418ca489f5b949f25f35abaa0 to produce "5f4dcc3b5aa765d61d8327deb882cf998c20828418ca489f5b949f25f35abaa0".
  • client: hashes this md5("5f4dcc3b5aa765d61d8327deb882cf998c20828418ca489f5b949f25f35abaa0") -> 09f3bb66a44153c4053857d4b57fdf3b and sends to server (step #4).
  • server: (having already done the above itself) compares 09f3bb66a44153c4053857d4b57fdf3b with it's own result, and (if they match) allows username to login (or begin a session, etc).

Note: This is a fairly naive way to implement challenges, as there's no temporal component of the salt it's vulnerable to replay attacks, etc. But you can ask more about that on Security.SO if need be.

9

GET http://api.example.com/users?usr=username

The returned JSON object would contain all the fields for the user (id, password, etc), so I could check the password in the client.

Congratulations, you broke the 3 fundamental rules of basic security at once!

  • never store clear passwords
  • never send them over http, but only https
  • never trust clients

Why not simply make a /login and /logout queries?

All subsequent REST calls will then submit the session-id as cookie to "proove" they're logged in. Session handling is something basic in most web frameworks. KISS.


...or would your argument be "because everything should be REST and stateless"?! Well, do you really think that makes sense? ...there are a lot of stuff that aren't REST and never will be. For instance /send_email, or /generate_token ...I think you misinterpret things into being too dogmatic that everything should be REST, etc. REST is for accessing your resouces, not doing actions, etc.


Lastly submitting your username/password each time isn't very different than submitting your session-id in a cookie. Is one really more stateless or stateful than the other? Yes. Does it make a difference? No.

As for the OAuth part, I heard it's so complex that authors of the original spec wanted to remove their name from it: http://hueniverse.com/2012/07/26/oauth-2-0-and-the-road-to-hell/

I doubt it's easier, and requires a session-id exactly like usual login.

2
  • Login: So what about sending only the username in the login querystring through https + basic https authentication, and receiving the profile (without password) in the response? Jan 29, 2016 at 13:57
  • @MisterSmith: if you want basic auth, you won't need a /login at all. It's sent "on top" of your normal requests. It can be "stateless" too if you want, by submitting your creds on every request. Just be sure to use https and preferably not to put them in the URL. ...Btw, I'm not really familiar with basic auth, it may not be as practical as a usual session login/token. (caching? logout? redirects?). But it's a valid option.
    – dagnelies
    Jan 30, 2016 at 8:36
9

For passwords, sending a hash copy of the password would seem to be your best alternative. Notice that this avoids two of the problems you are considering: the hash of the password disguises the "secret" that you are transmitting, and the result of the hash can be expressed in a reasonable character set so that you don't have to worry about encode/decode. (Note: we haven't actually added any security by doing this).

I'm suspicious about using the url to communicate the credentials -- why not use the Authorization header? You wrote:

Any other operation in the API will be passed the user name and password for authorization purposes

Were you expecting to embed the credentials in every api call?

the operation is not idempotent

Then the operation is a POST. The most common answer that I've seen is to expose a "token collection" resource, and add a new token by posting the request to that resource. Principle of least surprise should maybe lean you this direction.

But you might re-examine your assumption that the operation isn't idempotent. Clearly, you don't want to have multiple tokens in your table -- but if the database rejects the command to inject a duplicate token, you've got the idempotent behavior that you need. You'll want to be consistent with HTTP put semantics, which means that you'll want the resource you are PUTting to be unique to the token.

Note that the token resource and the token entity are two different things. You can "create" the resource first, and then (if the client application follows that link) create the token. REST doesn't care - as long as the client is following links provided in the hypermedia, everything is good.

I'm struggling trying to force tokens into being a resource.

It shouldn't be too difficult, resources are cheap.

For instance, if your token is an analog of an HTTP cookie (one of the parts of the web that Fielding calls out as not being restful), then a flow of requests from the client might look like

POST /A
-- the server does its magic here, generating and storing a new token, which
-- it wants the client to reference in its subsequent requests
redirect: /B?token=54321

GET /B?token=54321
-- Now the server knows that this is a request for resource /B within the
-- context of the specific token.  The representation of this resource
-- includes links to things that are also in the context of the token
returns: [representation including link /C?token=54321]

GET /C?token=54321
-- Subsequent calls stay in token=54321 space, until the server expires the
-- token and redirects the caller to some other representation of state.

Another alternative would be to separate out the reservation of the token identifier from the creation of the token

GET /A
-- here, the server generates a unique identifier for the token, without
-- doing any of the persistence work.
redirect: /B?token=54321

POST /B?token=54321
-- The client passes back to the server the id of the token, and the server
-- can choose to go create it and store it.  
-- Notice: we aren't posting to the token resource, we are posting to 
-- the resource identified by /B?token=12345; you get to decide what
-- that resource is.  If the representations are suitable, and you can make
-- the operation idempotent now that the token identifier is fixed, you
-- might be able to use PUT rather than POST
returns: [representation including link /C?token=54321]

GET /C?token=54321
-- Subsequent calls stay in token=54321 space, until the server expires the
-- token and redirects the caller to some other representation of state.

If you wanted to be extra careful, you might implement resources that distinguish a reserved token from a persisted token.

GET /A
-- here, the server generates a unique identifier for the token, without
-- doing any of the persistence work.  We're not making any changes to
-- the server state right now, so this is a reserved token.
redirect: /A?reservedToken=54321

GET /A?reservedToken=54321
-- this resource knows the token identifier, so it can now produce the
-- hypermedia control(s) that constrain the client to the reservedToken=54321 space.
-- We still haven't changed anything; this GET request is safe.
-- It's also potentially cacheable.
returns: [representation including link /B?reservedToken=54321]

POST /B?reservedToken=54321
-- The client passes back to the server the id of the token, and the server
-- can choose to go create it and store it.  
-- Notice: we aren't posting to the token resource, we are posting to 
-- the resource identified by /B?reservedToken=12345; you get to decide what
-- that resource is.  If the representations are suitable, and you can make
-- the operation idempotent now that the token identifier is fixed, you
-- might be able to use PUT rather than POST.
redirect: /B?persistedToken=54321

GET /B?persistedToken=54321
-- Now this resource knows that the token has already been reserved, and
-- can generate additional hypermedia controls that constrain the 
-- client to the persistedToken=54321 space.  Once again, this GET
-- request is safe and cacheable.
returns: [representation including link /C?persistedToken=54321]

GET /C?persistedToken=54321
-- Subsequent calls stay in persistedToken=54321 space, until the server expires the
-- token and redirects the caller to some other representation of state.

The client is just choosing from the hypermedia controls provided by the server. So while this history would look like

GET /A
GET /A?reservedToken=54321
POST /B?reservedToken=54321
GET /B?persistedToken=54321
GET /C?persistedToken=54321

You can later decide that if these resource identifiers don't comply with your coding standards, or that they are to difficult to follow, or simply that you are bored with them, then you can change them all

GET /A
GET /W?reservedToken=54321
POST /X?reservedToken=54321
GET /Y?persistedToken=54321
GET /Z?persistedToken=54321
5
  • The POST hack looks contrived. POST should be used to push client generated entities to a collection, but here I'd be posting an empty request with no data. Notice how there is no state transfer. And of course it is not idempotent unless you want to live in denial. Tokens are not client generated, but server generated. Jan 27, 2016 at 9:59
  • 1
    surely the client hashing the password isnt a good idea. If the client hashes the password the hash is the password. Knowing the hash allows access to the system (you just set your client to send it directly, not hash it) Jan 31, 2016 at 22:18
  • Yeah, I spelled "encrypt" incorrectly. Feb 1, 2016 at 2:36
  • More specifically, I was only thinking that part half way through. It disguises the secret, and saves you from the encoding problem, but it doesn't help protect the secret in anyway. You would still need HTTPS, and to worry about the URL getting logged, and... Feb 1, 2016 at 2:45
  • 2
    @RichardTingle You are absolutely right, and I have seen this suggestion many times before. People don't seem to understand that sending the hash of the password (and having the server validate it), is just as bad as sending the password in plain text. It just doesn't look like a password anymore, and it makes people feel better. But it's still a terrible practice (and now the password is plain text!)
    – Rob
    Feb 4, 2016 at 6:25
6

Typically your REST API should not support login functionality at all. Just require the caller to provide a bearer token from a trusted issuer in the Authorization header and be done with it. Use a well-known authorization provider like Google, Facebook or Microsoft Azure AD as your authorization server.

People don't use OAuth because it's mainstream. It's mainstream because it allows developers to delegate authentication and not have to worry about password storage and secure protocol implementation.

If you for some reason you have to implement this functionality in a REST service itself, I would just expose a /token resource over HTTPS to which you can POST a username and password. From that token request, return a digitally signed token that contains the information about the user the API needs, is valid for a limited time and that your API trusts.

1
  • Good point. I know in REST there should be no "traditional" login at all, and instead we should include authentication with each request. The problem is the UX guys came up with a login screen and I must call the server to check whether the user has an active account or not. Jan 29, 2016 at 21:22
2

Sending a password in the parameters isn't just "doesn't look right to me", that's about the worst thing you could do, and I (and everyone else noticing it) would stop using your service immediately due to a brutal security violation.

If you send the password back to the client, that is an even worse security violation: Your server must NEVER know the password. It must NEVER be capable of returning the password to the client.

A common method is this: You create a certificate for encryption. The public key is distributed with the client, the private key is on the server. Login encrypts username and password with the public key and sends them to you. You can decrypt the username and password on the server. Then you turn the password into a salted hash and check that it matches the username. If that is the case you create a random token and send it to the client. The client remembers the token and sends it with every request. You decide how long the token stays valid.

Basically every request requires the token (with obvious exceptions like login, create account, reset password).

It seems you got yourself into a bit of a state with tokens not being stateless. REST isn't dogma. Tokens are for authentication, they are outside of the rest of the REST API. They are supposed to be stateful. Nothing wrong with that whatsoever.

1
  • 1
    That asymmetric encryption you described; isn't that exactly what https does? Why reinvent the wheel? May 30, 2016 at 20:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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