Route Based Access Control

Dhruv Aggarwal
Practo Engineering
Published in
4 min readMay 16, 2018

Access Control is a very basic problem. There are a ton of libraries in Flask which offer the solution for implementing Role Based Access Control (RBAC).

In REST API, the resources are mapped to a particular entity in the DB. Thus, having POST access to a resource means that you have the access to upsert entries in the corresponding DB model.

But there are some inherent problems with this type of approach:

  1. What do we do when there are multiple Models that are being accessed from the API and you want to allow access to one but not all of them?
  2. What if the endpoint does not interact with the database?

In this article, we aim to provide an alternative solution to these problems.

IDEA

Instead of treating DB Model as the Entity for which access is managed, we attack the problem a bit differently by treating the resource(endpoint) itself as the Entity.

The premise is to manage who can access a particular resource. This makes the access management agnostic of what is happening inside the resource.

A resource can be defined as a combination of two things:

  1. Url (path)
  2. Method (action allowed)

Lets say that you have a POST endpoint which looks like /add/books. The user needs to have access to the entry which translates to this particular combination.

Lets take a deeper look into how we solved this problem.

BREAKING IT DOWN

STEP 1: IDENTIFYING THE INTERACTING PARTIES

This essentially means figuring out the basic entities that are interacting for access control.

  1. User: Models the user that is trying to access the resource.
  2. Role: Models the accesses/teams that the user is part of.
  3. Route: Models the resource that is being accessed. This is a combination of the above mentioned two things: URL and Method

STEP 2: DEFINING THE INTERACTIONS

Once we have identified the interacting parties, we need to define exactly how they interact with each other.

A User can have multiple Roles and there can be multiple Users with the same Role. Thus, User:Role interaction is a “M” X “N” interaction

A Role can have multiple Routes and there can be multiple Roles with the same Route. Thus, Role:Route interaction is a “M” X “N” interaction

ERD to outline the interactions between the entities

STEP 3: PLACING THE LOGIC

We have discussed the basic checks and schema that we need for implementing route based access control. But where should it be placed?

Ideally, we should expedite all authentication/authorization decision as early as possible. That said, it might often not be possible to do it without entering the resource.

There are two places where we can make this decision:
1. Application’s before request handler and skipping the endpoints where we do not want these checks to run (Whitelisting the endpoints to skip)
2. Decorator to all the endpoints where we want the authentication. (Blacklisting the endpoints to skip)

P.S. it is better the choose the first approach since forgetting to add decorator exposes your endpoint while failure to add the endpoint to whitelist only adds a stricter check thus ensuring no breach.

STEP 4: MAKING THE DECISION

Deciding if the user has access to a particular resource is pretty straightforward.

  1. For the current user, select all the available roles
  2. From the roles, get all the resources accessible
  3. Filter these results against the input request method and url
  4. If step 3 returns a result for the path being accessed, the user has the access to that resource

That is it! It is that simple!

TAKING IT FURTHER

We just went through a very simple approach to solve the access control problem by moving the decision making at the route level from just the role level. All the data is also stored in the database thus we do not need to maintain any such data in code.

But the above mentioned approach does not solve for all the cases. There are still many problems that are to be solved for. Listed below are some such questions that I asked myself and the solutions that I came up with after brainstorming with my friends:

Every API access means a call to DB to validate access

One word solution: Cache.
1. Create entries for each user in the cache (can use the session management cache as well for this) with the roles of the user.
2. Every time user logs in, fetch the user roles and store them in the cache.
3. Store the mapping between the role and route in the cache as well but not at the user level (since this data is not user specific)
4. Whenever a role/route is changed, update the role route map in cache
5. Refresh the user’s cache when updating their accesses

For every route added to the application, a db entry needs to be done

Use Regexes instead of exact URL. This way you can save a ton on route entries.

User level accesses are not supported

All the accesses are currently managed at the role level. The relation between role and route can be imitated for user and route to enable user level routing.

What if I want to open a route for a role for a specific period of time

Setting a simple expiry time for the role route mapping will do the trick

CONCLUSION

Route based access control offers us the benefits of becoming agnostic of the actual entities (models) being accessed and can be achieved by following the simple 4 step solution.

P.S. I have implemented the above strategy for ACL in Flask. If you want to see a working example, you can go to: https://github.com/dhruv-aggarwal/flask-rrbac and check the test cases.

Follow Practo Engineering on twitter for regular updates. If you like this article, please send us some applauses. This will help other Medium users find it. We are also looking for kickass developers. If you are interested, then visit here.

--

--