So you’ve decided to build authorization into your application. Sounds pretty straightforward, right? All it takes is a couple of tables in the database for roles and permissions, and we should be fine. Let’s take a deeper look at some design considerations you should be aware of from the get-go.
Authorization happens everywhere
The authorization mechanism gates every action or request a user makes in the application. Many (if not all) components in the application will need to make authorization decisions — which means we can expect to see authorization logic everywhere in the codebase.
As with any other aspect of an application — we need to think about separation of concerns: how do we ensure that the authorization logic is isolated from the rest of the application components? Without a clear separation of concerns, we can end up with a situation where the authorization logic is coupled with the rest of the application. We’ll have to make wide-ranging changes to the rest of the application with any updates to the authorization logic.
When using multiple services, the authorization challenge is even more complex. A typical pattern is that initially, the authorization logic will be repeated in every service — making it harder to update and maintain: every change will require updates in all the services. When more services are added, using more languages — this problem will be further exacerbated since now the authorization logic implementation will vary depending on the language used.
It’s more than likely that when the authorization layer is first created, it’ll include some form of roles and permissions (e.g., a user can read, an admin can delete, etc.). But as the application evolves, the authorization layer will need to be updated to support new roles, more fine-grained models, and other features.
This usually means that the authorization layer will have to be rewritten — and most likely more than once in the lifetime of the application. When the authorization layer is coupled to the application, it will surely mean significant rewrites to the application as well. So say goodbye to some precious months of development cycles spent on rewriting authorization code — which isn’t part of your core value proposition.
Since authorization decisions are made for every request or action in the application, they must be made in milliseconds. So ensuring that the authorizing component has the information it needs to make decisions within that time frame can be challenging since it will most likely require querying other data sources.
For example, even in the most basic scenarios, given some user identity information, the authorization component would have to resolve what roles are associated with that identity. In more complex scenarios, the authorization component would have to resolve additional user attributes or resource data to make its decisions.
To build a performant authorization layer, we have to architect the solution in a way that will allow the decision engine to fetch the data it needs in a reasonable amount of time.
Any authorization solution needs to be able to scale as the application grows and more authorization requests are made. If the authorization layer is not separated from the rest of the application, scaling will be a challenge. In addition, since the application will make authorization requests very frequently, every request will incur an overhead, which will make the authorization layer a bottleneck.
With that said — it’s hard to separate the authorization layer from the rest of the application since the decision relies on data coming from the application — and the application depends on decisions made by the authorization layer.
Authorization is tied to user identity. These days, applications are more than likely to use an identity provider (such as Auth0, Azure AD, Google Identity, Okta, etc.) to resolve the user’s identity. In these cases, integration with an identity provider will be a key part of the authorization layer and will present another challenge: since the identity provider is external to the application, it’ll be difficult to guarantee that it will be available at all times and that the response time will be acceptable. For that reason, some mechanism to synchronize the identity provider with the authorization layer will be needed.
But resolving the user’s identity is only the first step: the user’s identity has to be mapped to specific roles and, in some cases, to a set of attributes. An authorization solution will also require some way to store, maintain and serve these mappings to the decision engine in a timely manner.
Authorization is a key part of the security model of an application. In an enterprise setting, that means that all authorization decisions must be collected and audited. This could prove challenging to implement at scale: it requires instrumenting all the places where authorization decisions are either made or enforced. All the authorization decisions need to be aggregated in a format compatible with analysis and auditing tools.
In most organizations, the authorization process will include participants from outside the development team. This means that it can’t just be left as a series of if … else statements ingrained in the application code. Eventually, the authorization component will have to facilitate collaboration around the governance of the authorization process.
The challenge here will be to design the authorization solution in a way that will separate the authorization decision-making logic from the authorization enforcement logic. That way, authorization policies could be maintained and updated without requiring any changes to the application code. This will allow participants outside the development team to administer the authorization process.
While it might seem easy at first, creating an authorization layer is an involved process. It’s not just about creating reference tables in a database between users and their roles — it’s about creating a way to ensure that the authorization logic can be easily changed across the application and that it can continue to evolve as the application evolves. It’s about making sure the authorization decisions don’t slow down the application and that the authorization layer will scale as the application grows and the volume of authorization requests increases.
While many development teams may choose to initially build their own authorization solution, they may eventually realize that they’ve committed to more than they bargained for. The truth is that in most cases, while authorization is a must-have for any application to go to production — it is rarely a core part of the value proposition. Therefore, development teams should ask themselves whether they are willing to sacrifice valuable development cycles developing features that aren’t part of that value proposition.
If you’d rather avoid the challenges building an authorization system entails, we think we can help. Aserto provides an enterprise-ready, cloud-native authorization service deployed to the edge that is easy to integrate and will scale with your application. It delivers single-millisecond authorization decisions and is 100% available to your application. Aserto integrates with your existing identity provider and automatically synchronizes all the required authorization assets to the edge authorizer and provides an aggregated audit trail for authorization decisions. Sign up today and leave your authorization wows behind.