In his 2006 science-fiction novel The Three-Body Problem, author Liu Cixin alluded to a well-known fact in celestial mechanics: there is no closed-form solution to predicting the motions of 3 bodies under each other’s gravitational influence. The recent Netflix adaptation of the book brought this phrase to our collective consciousness.
Authorization may not be quite as hard as a 3-body problem, but it seems just as vexing in the identity & access world. This is because there are, in fact, three authorization components to deal with, and each “exerts gravitational influences” on the others:
- Policy Enforcement Point (PEP): the component that requests (and enforces) an authorization decision.
- Policy Decision Point (PDP): the component that computes an authorization decision using information passed in from the PEP, information cached in the PDP, and information retrieved from an external Policy Information Point (PIP).
- Policy Information Point (PIP): a provider of data that is used in authorization decisions.
Let’s go through each of these in turn, and describe the interplay between them.
Policy Enforcement Point
The PEP enforces authorization decisions. Three common policy enforcement points are the application itself, the API gateway, and the authentication service.
Let’s run through these options, starting with the application or service, which is the most natural place to enforce authorization decisions that grant or deny access to a fine-grained resource.
Application
Let’s look at Hubspot, a SaaS CRM platform, as a concrete example. With a SaaS product like Hubspot, permissions are granted to perform specific operations on various object types. For example, you can create, edit, view, or delete objects like Contacts, Ads, Campaigns, Marketing emails, Landing pages, Workflows, and so on.
For some items like Contacts, access can be restricted to items that belong to the Team that the user is assigned to, or only the specific Contacts assigned directly to the user. This is known as “fine-grained authorization”, because access rules can be defined down to an item level.
Realistically, the enforcement point in the fine-grained access control model offered by Hubspot must be each of the services that manage these objects. Before showing or granting access to an object, the service must determine whether the user has permission to perform that operation on that object. No other intermediary has the knowledge to do that.
One could argue that if an application like Hubspot is going to be the PEP, why not keep the authorization logic in the application as well, instead of externalizing it in a policy, and calling a separate Policy Decision Point to determine access?
This is a mistake for several reasons. Every application request needs to do some form of authorization before returning data to the caller. By treating authorization as a separate concern, you can handle it consistently across every call and every microservice in the application. This in turn makes the authorization system much more maintainable and easy to reason over.
This is exactly the conclusion that many tech organizations have reached, resulting in common authorization systems like Google’s Zanzibar, Airbnb’s Himeji, Carta’s AuthZ, Uber’s Charter, Netflix’s PACS, Intuit’s AuthZ, and countless others.
Externalizing authorization to a separate service also makes it possible to manage and change authorization rules in a single place, instead of having to sift through complex spaghetti logic in every route handler and every service.
However, not all authorization is fine-grained, and you don’t always have the ability to easily change the application logic to incorporate a call to an external PDP. What other possible PEPs exist?
API Gateway
Another popular enforcement point is the API gateway. The gateway provides a control point for passing along or blocking requests, allowing for rate limiting, circuit-breaking, and checking that the requests are well-formed.
Similarly, a gateway could perform authorization on the request before forwarding it on to the application. The gateway logic can reason about fields like the authorization header, which often contains a JWT, out of which the user/subject could be extracted. It also has access to the method, path, and body of the request.
Building authorization logic in the gateway may be appropriate for medium-grained access control. For example, if a user is known to be a “viewer”, the gateway could block PUT or DELETE operations. But there’s a limit to how fine-grained this enforcement can be in practice - in the limit, you’d have to understand how each field in the request maps to the data that is needed for authorization, or perhaps even reach into the application’s database to retrieve additional data. This creates a tight (and brittle) coupling between the gateway code and the service or its database - almost certainly an anti-pattern.
So using the gateway as a PEP is appropriate for coarse- or medium-grained authorization, such as when a platform services team wants to build authorization enforcement consistently across services, but may not have the ability to change (or influence) the application code for each service.
Authentication Service
The last common enforcement point is the authentication service. When a user logs in, a call can be made to a common authorization service which can evaluate an authorization policy. The authentication service can then embed claims / scopes in the access token that it mints for the user, or fail the authentication if the user isn’t authorized to access the system.
This is the most coarse-grained form of authorization, since you don’t really have a fine-grained resource you’re trying to protect at authentication time. All you know is who the user is, and what service they’re trying to access.
However, it does have the advantage of creating a “first line of defense”, without having to change any application logic. It can be a good first step in utilizing an externalized authorization system consistently across all services.
In summary, there are tradeoffs associated with picking any of these enforcement points. But they can all be used together (following the principle of defense in depth), and each can utilize the same Policy Decision Point - leading to consistency and reuse.
Policy Decision Point
The PDP is called by the PEP and evaluates decisions on its behalf. This sounds simple in principle - just move all the decision logic from the PEP to the PDP, right? In practice, this gets a bit complicated because of a necessary ingredient in the decisioning process - data.
When the decision logic is embedded in the application service, the app simply stores and retrieves the data it needs to make an authorization decision from its own database. For example, a user may be a member of a group, which grants them certain permissions. A user may also be an owner of an item, which entitles them to perform other actions. Most services have tight coupling between the data they manage and the ownership of that data.
So when the application instead calls a PDP to make an authorization decision, that data needs to come from somewhere:
- It may be passed in by the application, in which case the app needs to retrieve all the data that’s necessary to make the authorization decision, and the PDP is simply a stateless logic processor.
- It may be stored by the PDP, as static or cached data. This means that the PDP needs to either have a copy of the data (which must be kept in sync with the source of truth), or it needs to become the source of truth for the data.
- It may be retrieved dynamically by the PDP from another source, often called a Policy Information Point, or PIP. The PIP may even be the original application database… but this leads to some “interesting” architectures, where the PEP calls the PDP to get a decision, and the PDP calls back into the PEP (or its database) to obtain the data necessary to make the decision.
Indeed, the data architecture is the most important thing to get right if you’re externalizing authorization to a separate PDP. And this brings us to the final component - the PIP.
Policy Information Point
A PIP is any data source that contains information needed to make an authorization decision. For example, a corporate directory (LDAP / Active Directory) is the source of truth for user properties and group assignments, which are often used in authorization decisions.
A naive implementation of an authorization architecture would have the PDP make a call to every such information point as it’s evaluating an access decision. But this adds latency and availability concerns to the authorization process - every such call would incur additional latency, and potentially result in a “deny” if the PIP isn’t reachable within a reasonable time window.
In practice, the data in PIPs is either cached by the PDP, or imported as an “extract database” so that the PDP doesn’t need to make a real-time external call to evaluate a decision.
Architectural tradeoffs
To recap, an authorization system has three primary components - the PEP, the PDP, and the PIP.
If the PEP is the application itself, then it’s likely you want a PDP that sits right next to the app service (e.g. deployed as a sidecar), and have that PDP cache all the data that’s needed for authorization. This means you’ll need a way to push data to the PDP as it changes - for example, the group assignments for every user, or the ownership information of each item that the service manages. In this architecture, the PIP would not be involved in the real-time evaluation of an authorization decision - the data it manages would be imported/cached by the PDP and updated in near-real time as it changes.
If the PEP is an API gateway, then your latency budget is relatively small, and you’re only going to do medium-grained authorization. You’re still going to call a PDP, and potentially retrieve user/group assignment data from a PIP, or choose to cache that data in the PDP.
Finally, if the PEP is the authentication system, you know that authentication happens once in a session as opposed to in the critical path of every application request. Therefore, you can incur a bit more latency. In addition, the PIP is almost certainly the user management / authentication system itself, so the data it manages (e.g. user attributes and group assignments) can be provided to the PDP as part of the authorization request.
In Summary
Authorization may not be as hard as predicting the motions of 3 bodies under each other’s gravitational influence, but it does have some parallels. The design of the PEP, PDP, and PIP influences the overall architecture, and each exerts gravitational effects on the others.
If you’re interested in an authorization service that can fit any of these scenarios, check out Topaz, an open-source PDP that is fast, flexible, and easy to incorporate.
Finally, if you’d like to chat about your authorization challenges, we’re here to help! Set up a meeting with one of our engineers, or join our community slack!
Related Content
OpenID Foundation AuthZEN Working Group Announces Interop Results
Conformance with the AuthZEN request/response protocol marks milestone in simplifying and standardizing authorization approaches.
May 28th, 2024
Implementing Custom Roles in your SaaS Application
Custom roles are tricky to implement. This post offers two approaches for allowing each tenant to add custom roles: one for simple RBAC, and one for fine-grained ReBAC.
Jun 20th, 2024
An “easy button” for API Authorization
Scaling a fine-grained authorization model for APIs can be tricky, especially when you have hundreds or thousands of them. Fortunately, Topaz makes it easy!
Jul 8th, 2024