7

A lot of examples about RESTful Web Services do not take into account the problem that today many applications are multi-user.

Imagine a multi-user backend exposing a RESTful API. The backend data architecture uses a shared database and shared schema. Each table will contain a reference to a tenant_id:

+-------------+----+-----------------+
|  tenant_name| id |   shared_secret |
+-------------+----+-----------------+
|         bob |  1 |   2737sm45sx543 |
+-------------+----+-----------------+
|       alice |  2 |   2190sl39sa8da |
+-------------+----+-----------------+

+-------------+----+-------+-----------+
|    pet_name | id |  type | tenant_id |
+-------------+----+-------+-----------+
|       fuffy |  1 |   dog |         1 |
+-------------+----+-------+-----------+
|       kerry |  2 |   cat |         2 |
+-------------+----+-------+-----------+

Question 1: with three or more client applications (i.e Android, iOS and Web App) interacting with the RESTful backend, how would you perform authentication against the backend?

RESTful backend, API, HTTP-Verbs, shared database and schema 
|
|
+---- Web Application (Client 1)
|     |
|     + Alice
|     |
|     + Bob
|
+---- Android Application (Client 2)
|     |
|     + Alice
|     |
|     + Bob
|
+---- iOS Application (Client 3)
|     |
|     + Alice
|     |
|     + Bob
|

Each client should allow Alice and Bob to manage her/his pets. Each client is a GUI, and it's going to use (internally, making HTTP requests) the backend. Question: how can each client can authenticate against the backend?

Assume HMAC (it's perfectly RESTful, no sessions): this method involves signing the payload with a shared secret (never sent over the wire). Should each client have its own copy of the tenant table (which holds the shared_secret field)?

Android App -> Client Sign -> Signed Request -> Backend -> Result
    Web App -> Client Sign -> Signed Request -> Backend -> Result

Question 2: what should the resource URI's look like?

Here are two possibilities for ways to GET Bob's pets:

Possibility #1: The Authorization header gives you the tenant's (unique) name:

GET /pets HTTP/1.1
Host: www.example.org
Authorization: bob:c29kYW9kYSBhb2lzYWRoIGYgZDUzNDUz

Possibility #2. The tenant_id is sent as query parameter:

GET /pets/tenant_id=1 HTTP/1.1
Host: www.example.org
Authorization: bob:c29kYW9kYSBhb2lzYWRoIGYgZDUzNDUz
willscripted
  • 1,438
  • 1
  • 15
  • 25
gremo
  • 47,186
  • 75
  • 257
  • 421
  • Be careful about using the phrase multi-tenant. Im not convinced it means what you think it does. See details here http://msdn.microsoft.com/en-us/library/aa479086.aspx – GWed Mar 04 '13 at 19:23
  • @Gaz_Edge it means virtually partition data among clients and a single instance will serve more clients, that is what I'm doing. Do you agree? – gremo Mar 04 '13 at 20:28
  • 1
    I think you are overcomplicating this. You have 1 web service, n web service clients and x users yes? – GWed Mar 04 '13 at 22:28
  • 1
    @Gaz_Edge exactly. How would you simplify than? – gremo Mar 04 '13 at 22:46
  • Ive provided a simple way in my answer. Do you feel my answer wont give you what you want? If so, please explain and I'll try and help. – GWed Mar 04 '13 at 22:56
  • What you are describing is a relatively common application need -- it is not nearly as complicated as you might think. – David J. Mar 08 '13 at 17:50
  • There's one thing in your question that worries me: You do not actually consider giving client programs the complete list of users and secrets, do you? – Christopher Creutzig Mar 09 '13 at 09:46
  • @ChristopherCreutzig that's a related issue I'm talking about... – gremo Mar 09 '13 at 22:59

4 Answers4

4

Part 1

(Thinking out loud: Have you already decided to use HTTP and HMAC? If so, why are you asking us?)

I'd suggest HTTPS with Basic Auth. Simple. It is good enough for Stripe after all.

References:

Update: Here is some additional detail on how to handle the auth:

  1. Each client application will contact the service using an API key. Using HTTPS and Basic Auth, the client will provide its API key as the basic auth username. It does not need to provide a password, since it is using HTTPS. You need to assign an API key to each application (Web app, Android, iOS), I see two ways:

    A. One option is to give each user one API key that is shared across clients.

    B. Another option is to give each client a unique application.)

  2. But how do you get the keys to the clients in the first place? Build a "key request" API endpoint. I'd suggest giving each client a "starter" key that is only used to contact this endpoint. (The starter key does not grant other access.) When a user first uses the client, he/she must authenticate. The client passes this along to the "key request" endpoint so it can generate a key associated with the user. From then on, each client has a client-tied API key.

Part 2

Consider giving each tenant a subdomain. If you use Rails (or probably any modern web stack) you can use the subdomain to lookup the tenant id. Then your API can be used like this:

GET http://tenant1.app.co/pets
GET http://tenant2.app.co/pets
GET http://tenant3.app.co/pets

References (Rails-specific but should be helpful across web stacks):

Note: as your example suggests, for simplicity, I would not re-use the same pet id for different tenants. For example, the following is a simple way to go:

GET http://tenant1.app.co/pets/200
GET http://tenant2.app.co/pets/201
GET http://tenant3.app.co/pets/202

The approach I'm describing is much cleaner than passing tenant_id as query parameter. Besides, using tenant_id as a parameter feels wrong. I like to use parameters for more "algorithmic" things, as I read in "RESTful Web Services" by Ruby and Richardson.

References:

Community
  • 1
  • 1
David J.
  • 31,569
  • 22
  • 122
  • 174
  • I like the part 2, I'll go for subdomains. But part 1 does not explain the challenge of authenticating each application against the backend. Maybe it's my poor English and I'm not able to explain the problem in deep. I've chosen HMAC because there is no state between requests. Why you are suggesting Basic? – gremo Mar 08 '13 at 15:18
  • I suggested HTTPS + Basic Auth because it is the simplest thing that will work. I provided references if you want to dig into all the pros and cons. – David J. Mar 08 '13 at 17:48
  • @Gremo, I just added a few extra details about part 1 (auth). – David J. Mar 08 '13 at 18:07
  • Thanks for the update, I've ended the bounty. I'd love to see the first part of your answer explained using HMAC, if you have some time. – gremo Mar 09 '13 at 23:03
3

By 'multi-tenant' do you simply mean application/web service users? Multi-tenant can often mean something much more complicated msdn.microsoft.com/en-us/library/aa479086.aspx.

You need to authenticate each user with your web service. This can be done with basic http authentication across SSL.

From the web services point of view you would perform authentication the same for all three clients. The service doesn't care about the type of client - that's the point. It may be that you require different representations for your clients e.g. XHTML or JSON. I like to keep things simple and always choose JSON.

For the resources, the easiest way to manage them is to have a users resource as the top level, then chain all the resources together for each user e.g.

GET users/fred/pets - returns all pets for user fred
GET users/fred/pets/sparky - returns details on freds pet sparky

The great thing about doing it this way is that you can add code to authorise each request e.g. you may have two users, fred and jack. Both users will pass authentication, but you should only allow fred to request his resources and jack to request his. You just have to add authorisation checks in your api e.g. get the username from the URI, get the username of the authenticated user, check they are the same. If not return something like http 403 forbidden, if they are the same, allow the request.

I think if you are still unclear, you need to read up on the details of REST. By far THE best book on the subject is this one RESTful Web Services. It covers REST from first principles. It also has a very nice section on designing resources, and how to manage users and multiple clients.

GWed
  • 15,167
  • 5
  • 62
  • 99
  • I'll edit the question to make it more clear. In the meanwhile: i have just one web service (the RESTful backend) and `n` possible clients (apps) consuming it. Users (tenants) will use one of the `n` clients (maybe even more, i.e. the webapp and the android client). – gremo Mar 04 '13 at 17:49
  • I want to give Gaz an upvote just so s/he doesnt have a score of 666. – Glenn Ferrie Mar 08 '13 at 14:11
2

I'm not sure to understand the question since multi-tenancy seems a bit overkill in this case. However I can try to answer the second question.

REST is a "resource-based" architecture, and you must realize that /pets and /pets/?tenant=1 does not refer to the same resource:

  • /pets refers to the pets of the current user,
  • /pets/?tenant=1 refers to Bob's pets.

While neither solution is wrong, it is usually better to get the second solution. URIs are indeed designed to be shared, and you have more reasons to share "Bob's pets" (even if it needs authentication and authorization to be represented) than the abstract "my pets" which would be different for every user.

See Should resource ids be present in urls? for a similar discussion...

Community
  • 1
  • 1
Aurélien Bénel
  • 3,775
  • 24
  • 45
1

If HTTP is the protocol all 3 client-types use, you should only need to implement one authentication scheme. You can pick your poison - Basic, Digest, Oauth2, or Cookie are some oft used methods. Since it is over HTTP, I see no reason to replicate this logic. There are probably several frameworks that will abstract this for you depending on your platform.

To distinguish the client type, you can use a HTTP header. The user-agent might already make this possible. Another option would be to use a custom header you define. Any HTTP client is capable of setting headers, and any server can handle custom header. A decent web framework will make those available to you with relative ease. As a back-end service, I suspect you will want to handle all client requests uniformly - or as much as possible. Certainly, maintaining one backend is preferable to three.

As far as what your API should look like - it is entirely up to you. To stay RESTful, how to GET a cup of coffee is well worth the read - insert obligatory link to Roy Fielding's thesis. Chances are, what you are really looking for is a guide to writing 'resourceful' links.

Of the options you list, prefer the 2nd,/pets?userId=bob, if users might need to access all the pets in the system. Prefer the first, /pets if a user will only ever need to access their pets.

Community
  • 1
  • 1
willscripted
  • 1,438
  • 1
  • 15
  • 25