14

After creating a basic REST service, I've have come to the point where it would be appropriate to add some sort of password protection, as I need to verify that my users are both properly logged and have sufficient permissions to execute whatever action they are going to.

The REST service will mainly be accessed from a Javascript-heavy frontend and with that in mind, I have come up with the two following alternatives to solve this:

  1. Make users login by first sending credentials to a /login page with POST. The page sets a session cookie wherein the user is marked as logged in, along with the permission level. On each following request, I verify that the user is logged in and his/her permission level. When the session expires, automatically or manually (logout, the user will have to re-logon).

  2. Temporarily save the credentials hashed locally and send the users credentials along every single request made by the user to verify the credentials & permissions backend on a per-request basis.

Are there more ways to solve this and is there something else that I should be concerned with?

Industrial
  • 41,400
  • 69
  • 194
  • 289
  • If your backend is in Java you could also use [Apache Shiro](http://shiro.apache.org/), it will nicely solve most of your problem. – pierpytom Mar 29 '16 at 14:04

5 Answers5

19

I'm currently developing a REST API along with a client (written in javascript), below I'll try to explain the methods used to protect the API against unauthorized access.

  • Make your REST API to require a Auth-Key header upon every request to the API, besides /api/authenticate.

  • /api/authenticate will take a username and a password (sent using POST), and return user information along side with the Auth-Key.

  • This Auth-Key is randomly generated after a call to /api/authenticate and stored in the backend users table with the specific user entry, a md5 hash of the remote ip + the user agent provided by the client.

  • On every request the value of Auth-Key, and the md5 sum mentioned, is searched for in users . If a valid user is found that has been active during the past N minutes the user will be granted access, if not: http return code 401.

  • In the REST client, first get the Auth-Key by posting to /api/authenticate, then store this value in a variable and send in on every future request.

Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • Thank you for your answer. How do you handle expiration of the keys + eventual automatic renewal? – Industrial Dec 19 '11 at 17:55
  • @Industrial The expiration of keys is handled by another field called `last_access` in the `users`-table, when searching for the specified `Auth-Key` I also have a `WHERE` claus saying; `WHERE last_access > (UNIX_TIMESTAMP()-(N*60))` – Filip Roséen - refp Dec 19 '11 at 17:59
  • @Industrial Are you looking for a more elaborate answer, or is the current post sufficient? (I'm asking because it's not marked as accepted, and if you'd like I can provide you with more information) – Filip Roséen - refp Dec 19 '11 at 18:27
  • Thanks again. Well, I would like to hear how you handle the situation of when a key expires, as I guess that it's `last_access`-timestamp isn't updated on each request? – Industrial Dec 19 '11 at 18:44
  • @Industrial It is updated on each request, the auth-key is a unique key and the query to update the timestamp will not create any significant overhead, so doing such a query on every request is not bad at all. – Filip Roséen - refp Dec 19 '11 at 18:55
  • Oh, so on every request, the `Auth-Key` is validated, recreated and sent back so the process can be repeated. Makes sense really and the (little) overhead it causes may very well be worth it! Thank you for your answer to my question and my comments! – Industrial Dec 19 '11 at 21:52
  • @Industrial No, the Auth-Key is not recreated to ease implementation client side though that wouldn't be an issue. Also; You don't need to do a `SELECT` and an `UPDATE` query, just do `UPDATE` with the where claus described and see if any entry was updated, if not; user didn't provide a correct Auth-Key. – Filip Roséen - refp Dec 20 '11 at 00:00
  • Ok, Now it makes sense. So only the `last_access` would then be updated per request and the `Auth-Key` stays "static" per session? – Industrial Dec 20 '11 at 08:41
  • 1
    @Industrial yes, and the session will expire in N minutes because of the way it's implemented. – Filip Roséen - refp Dec 20 '11 at 08:58
  • I don't really like the use of '/authenticate' because it's not a noun/resource. Shouldn't the intention be signalled through the method (e.g. POST)? The first other alternative I thought of was '/session' but this seems a little wrong given that it's REST :) – occulus Jan 31 '13 at 17:05
  • I don't think your approach meets with the statelessness constraint of REST. The server should not maintain client session, and you surely store the information "loggedIn" on your server... – inf3rno Sep 07 '14 at 12:19
3

If you want to stay true to the definition of a REST service then it should be stateless and not store any login (or other context specific) data on the server: http://en.wikipedia.org/wiki/Representational_state_transfer#Constraints

Your 2nd approach would fit this model

Macros
  • 7,099
  • 2
  • 39
  • 61
2

First decide what it is that you're protecting against:

  • Authentication? (Knowing who is requesting your service?)
  • Authorization? (Whether a given person can properly request a given service or not?)

I recommend that you provide hashed keys for your service. That way you can manage the key issue separately from the services. Or a client key and a secret, Amazon does this.

It is always easier for the client if you have a stateless protocol. And send everything through the parameters, cookies are a bother for the client too.

Remember that it is in your interest to make it as easy as possible for potential developers to use your service. A super secure service that no one uses is boring.

You can let clients choose the security level by giving them the choice of HTTP or SSL/HTTP endpoints to connect to. Client choice is a good thing.

Larry K
  • 47,808
  • 15
  • 87
  • 140
1
  1. Make users login by first sending credentials to a /login page with POST. The page sets a session cookie wherein the user is marked as logged in, along with the permission level. On each following request, I verify that the user is logged in and his/her permission level. When the session expires, automatically or manually (logout, the user will have to re-logon).

  2. Temporarily save the credentials hashed locally and send the users credentials along every single request made by the user to verify the credentials & permissions backend on a per-request basis.

Your first approach does not meat the statelessness constraint of REST. You cannot maintain client sessions on server side. This constraint makes REST highly scalable...

Your second solution is appropriate. The simplest way to use HTTP basic auth. You don't have to hash the password on client side. What you need is an encrypted connection. On server side you can have an [username, password] -> [identity, permissions] cache, so this solution is much faster and superior in every other way than having server side sessions.

By 3rd party (non-trusted) clients the authentication is more complex, I guess you don't need that part.

inf3rno
  • 24,976
  • 11
  • 115
  • 197
0

I'm no security-expert. I use the RESTful Play!-webframework and they do the following things to authenticate users.

  1. The cookie is protected against manipulation. It is signed with a long secret key and is checked for each request. Just hashing it is not enough!
  2. They recommend to set unique information the identify the user in the cookie. As the server should be the only one to manipulate the cookie, that's enough.
  3. Don't put the password as credential in the cookie. If someone sniffs the cookie, not only the session can be hijacked, but also the complete account or even worse, other accounts with the same credentials.

If you want to protect the cookie against hijacking using https.

Tilman Schweitzer
  • 1,437
  • 10
  • 10