Friday, September 30, 2011

Access Control for Your RESTful API

Problem

When your RESTful API gains popularity and different types of client applications (browsers, mobile kits, desktop UIs, other programs) start consuming it, it becomes apparent that they are all using your interface differently. Moreover, they all should have different set of resources available for them. Some applications only need to read couple of resources while others need read/write access to most of your resources. At that point, coming up with a flexible access control scheme becomes crucial. You could try to identify the application that is consuming your interface and based on that only allow access to certain resources, but that would be too easy to break. The only secure way of doing the access control, is to check the authorization of each request. This means tying the access control to the logged in user. However, even then there are usually different types of users (groups with different permissions) accessing the interface.

Let's take an online book store as an example. The API supports three different user groups:

  • Administrator has access to every bit of information supported by the API 
  • Merchant may add and remove books from the store
  • Buyer can place orders, cancel them and modify her account details
We also have to deal with more fine grained access control situations when, for example clients fetch a list of orders from the system. What should be requested and what should be returned? They both can do GET request on /order/ resource but most likely the result will look different for Merchant and Buyer. So, even if the request looks the same, Merchant will see all orders made to her shop whereas User will only see her own orders. Granted, we could alleviate this by providing different URLs for each list (e.g. /shop/123/order/ and /user/jedi/order/) but usually you end up in a situation where you need to filter the results based on user's authorization anyway.

Even more complicated access control rules are required when you actually need to filter out some of the properties from representations. An example of this might be when a client requests user information (GET on /user/jedi/) and some of that information -- such as SSN or credit card number -- should be hidden from Merchant but visible to the User.

Solution

What I've come up with so far, is what I called 4-tier filtering. This access control scheme has the following tiers:
  1. Filter resources -- possibility to hide resources from a user
  2. Filter methods -- possibility to define which methods (post/get/put/delete) are allowed for a user
  3. Filter resultset -- possibility to filter the result of a collection type* resource
  4. Filter properties -- possibility to leave out some of the properties (of the representation)
* by collection type resource I mean resources that return list of elements (e.g. /order/).

Implementation

Not many REST-frameworks take special interest in fine grained and flexible configuration of access control (please, post links in comments if you can suggest some). Of the 4 tiers defined above, the first two are fairly easy to implement generically while tiers 3 and 4 are much more difficult. What I've been using for the first two tiers so far is a Django middleware -- codenamed Bulldog -- that I implemented. I should mention that there's nothing Python or Django specific in this approach; it should be applicable in any environment (I'm actually in the process of doing the same thing in node.js).

Bulldog combines the first and second tiers of the access control in a way we have come to know from relational databases. This requires having users, groups and permissions. The implementation utilizes django's built-in support for these entities. Bulldog defines four permissions (CRUD) for each resource that the given interface supports. These permissions are then assigned to users and groups. A user automatically receives all permissions from the group she belongs to. So, in the permission table we have permissions like (these are automatically populated by the middleware):

resource_order_post
resource_order_get
resource_order_put
resource_order_delete
resource_user_post
....

The format being resource_<resource name>_<method>. The resource_ prefix is used for making a distinction between permissions dealing with access control tiers one and two and all other permissions. User may have any combination of these resources. That is, she may be able to GET order, but not UPDATE it and she may be able to DELETE a user but not POST (create) it.

By default Bulldog denies all access to any resource. Only if the user has been granted with any of the resource_* permissions, she is allowed to access those resources. This being a django middleware the incoming HTTP request never sees the REST framework let alone the API implementation if it doesn't have proper authorization.

Next

I haven't yet figured out what's the best way to implement tiers 3 and 4 generically but obviously I'll need a different set of permissions and the implementation will probably need to grant everything by default (as opposed to denying everything).

In theory, tier 3 could also be implemented as a middleware by filtering the resultset before returning it to the client. In practice however, this would be terrible waste of CPU and memory because client would potentially end up doing a full table scan into memory and then filtering out most of the records. More likely, access control for tier 3 will have to be presented using a specification pattern and that specification instance is passed along with the request. Later on, a repository or another source of data can use the specification to filter the data.

Now, in order to implement tier 4, the REST framework should provide means to define representations using for example, JSON schema or similar. In that definition, I could add annotations (required permissions) for the properties that are subject to access control.

I'll give this some more thought and write a followup with (hopefully) some code...

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. I asked the same here: http://stackoverflow.com/questions/18622678/rest-api-use-endpoint-properties-in-authorization-model

    I think you can use additional filters by every permission. After that you can easily filter the properties, just check whether the user has permission to a subresource, or a projection. For example you can give a permission like this "GET /users/* filter:isOwner projections:uri,basic". After that you can check that user is the owner of the account you want to see or not. And you can give permission if she wants uri or basic projection. If she is looking for full projection you can send 403 response, or basic projection if you have an ordered list of projections. It's not so simple, but it could work. I'm developing a similar system in php. It is much better with this approach than hardcode the permissions...

    ReplyDelete
  3. When your website or blog goes live for the first time, it is exciting. That is until you realize no one but you and your. security alarm

    ReplyDelete