Author: Gabriel L. Manor Director of DevRel, Permit.io
Bio: Gabriel is a senior full-stack developer with a favorite kid named Frontend. For over 10 years now, he’s enjoyed writing clean code, simplifying complex problems, leading feature development and influencing innovation every day. When not busy with code, you’ll find him talking about application performance, building confidence in code-bases, product architecture, developing organizational culture and other nerdy dev stuff.
As microservice architecture gains popularity, it becomes increasingly important to ensure that the services and their interactions are secure. One critical security aspect is authorization, which refers to determining whether a user or service has permission to access a particular resource or perform a specific action.
Authorization in a microservice architecture is more complex than in a monolithic one. In a monolithic architecture, a single authorization point typically controls access to all resources. Instead, a microservice architecture has multiple services, each with protected resources.
Furthermore, each service may have different, independent authorization requirements, and enforcing these requirements in a consistent and scalable manner can be a challenge.
For example, a user with permission to access resources from one service may not have permission to access another, even though both services belong to the same application.
In this article, we will overcome these challenges by using essential best practices one should follow when creating a microservice architecture. By implementing these best practices, you can ensure your microservice architecture is secure, compliant with industry regulations, and users can only perform the actions they should have access to.
Let’s dive in!
Create Standalone PDPs
A Policy Decision Point (PDP) is a component that makes authorization decisions based on policies and context information. It evaluates the incoming request against a set of policies and decides whether to allow or deny it. In a microservice architecture, it is essential to keep the PDP standalone, separate from application components. Therefore, the PDP should have its own control and data planes, which can be independently scaled, managed, and secured.
Decentralizing the PDPs reduces the impact of any failures, increases availability, and enables horizontal scaling of the authorization process.
Separating the control plane from the data plane means that the PDPs can have dedicated resources – including CPU, memory, network, and storage, as needed for their particular operations. These enable better performance and reliability for the authorization process. It also allows managing and updating the PDP independently of the application components. For example, if there is a new need for adding, editing, or modifying policies, it can be done without disrupting the application components.
An example of a standalone PDP is Open Policy Agent (OPA). OPA is a policy engine that enables fine-grained control over resource access in a microservice architecture. OPA has an implementation of a standalone PDP that evaluates policies and generates telemetry data. The PDP can be scaled independently of the application components, providing better availability and performance.
Use Sidecars for PDPs
In a microservice architecture, a sidecar design pattern is a common approach for providing additional functionality to application components. This design pattern is accomplished by deploying a sidecar container alongside each application component to deliver specific services or features. Using a sidecar design pattern for managing PDPs, we ensure that each application component has its own dedicated PDP, which can be independently controlled and updated.
The sidecar pattern benefits applications that require low latency and high data reliability. Suppose an application component needs to make multiple requests to different microservices. Having a local PDP that can quickly evaluate the policies and provide the authorization decision could be beneficial in reducing network latency and improving the application’s performance.
Decouple Policy from the Enforcement Point
The other side of the Policy Decision Point (PDP) is the policy enforcement point (PEP). The application enforces a permission decision made by the PDP and enforces the proper action a user is allowed or not allowed to perform.
In a monolithic application, it’s common to embed the authorization logic in the application code itself. The code checking whether a user is allowed or not to act on a resource is also responsible for enforcing policies and making authorization decisions. While this approach may work, it aligns differently from SOLID principles as it mixes concerns and allows for repetition.
Decoupling the policy logic from the enforcement point in a microservice architecture is even more essential. This separation of concerns is critical in reducing the complexity of the authorization process, enabling communication with PDPs and better lifecycle management of policies . It also keeps the application code focused on the functional logic instead of mixing in the permissions where they’re irrelevant.
Using this approach, if we need to add or update the policy, we can keep it in the authorization control plane without changing the application’s code.
Create a Straightforward Enforcement Framework
Creating and enforcing a simple check function in the enforcement point is essential to keep the PEP decoupled from the policy logic. The check function should have a single function – validate the request and send it to the PDP for evaluation. The PDP should be responsible for making the authorization decisions and sending the response back to the PEP. This design ensures that the authorization logic remains centralized and easy to manage. It also ensures that the authorization process remains simple and easy to understand.
As part of Permit’s authorization as a service product, we continuously release SDKs for many programming languages to help the users to communicate with our provisioned PDPs. The check consists of one function that gets three parameters, Identity, Action, and Resource, and sends an API call to PDP. We also encourage our users to build compound conditions during the configuration phase, instead of doing it while analyzing the permission check.
Use Policy as Code
Policy as code is an emerging best practice for managing policies in a microservice architecture. It involves expressing the policies in a declarative format, such as JSON or YAML, and checking them into version control. This approach enables better lifecycle management of the policies, as you can manage, update, and version them like any other piece of code.
Policy as code also enables better granularity for authorization, making defining policies more robust. For example, you can start with a basic model with the user’s role or group (Or, Role based access control – RBAC), and as your needs grow, support seamlessly also in creating policies based on specific attributes/properties of a user or resource (Or, Attribute based access control – ABAC), resulting in more fine-grained control over access to resources.
One example of a Policy as Code implementation is Open Policy Agent (OPA). OPA provides a declarative language called Rego for expressing policies and a runtime for evaluating them. The policies can be checked into version control, managed, and updated like any other code. OPA supports a wide range of policies, including RBAC and ABAC.
Cache Contextual Data in PDP
Not all data required for authorization is available or relevant for every authorization check request. Some data, such as user attributes, object metadata, and entitlements, may need to be fetched from external sources such as IAM providers, application databases, external services, etc. Bringing this data for every request can be slow, inefficient, and may introduce latency into the authorization process.
Using a policy administration tool that fetches and updates the PDP as changes in the external resources occur makes policy evaluation faster and more efficient. The PDP can quickly access the required data without fetching it from external sources.
One example of such an administration tool is OPAL, an open policy administration layer that works with OPA. OPAL tracks changes in external services and propagates the data to the OPA PDPs so the authorization requests can handle existing data and return faster results.
Support Multiple Models such as ABAC and RBAC
The typical Role Base Access Control (RBAC) approach can be limited in a microservice architecture as we want to develop independent services and maintain their data models. Creating general roles and resource standards can be complex and exhausting. For such cases, we might want to add permissions models, such as Attribute Based Access Control (ABAC) or Relationship-Based Access Control (ReBAC), that allow more granular policy configuration and authorization checks.
Following patterns such as policy as code and independent PDPs, you can start with a simple RBAC model and integrate ABAC or any other model seamlessly into the application when the need arises. Since the PEP code is always kept as simple as one function checking for permissions by Identity, Resource, and Action, the permission model in the control plane stays valid from the application side. Policy languages such as Rego or AWS Cedar support multiple permission models in the same PDP implementation.
Authorization is a crucial aspect of a microservice architecture. It enables organizations to control resource access accurately, ensuring that only authorized users can view sensitive data. However, implementing authorization in a microservice architecture can be quite challenging, as it requires a different approach to authorization from monolithic architectures.
In this blog post, we have outlined seven best practices for implementing authorization in a microservice architecture. By following these best practices, organizations can implement authorization in a microservice architecture that is resilient, flexible, and efficient. These best practices enable organizations to control access to resources in a granular way without introducing unnecessary complexity into the architecture.
Besides implementing solutions, organizations can ensure they align to secure authorization by using authorization as a Service product such as Permit.io, which provides full-stack support for authorization in microservices and implementing the best practices outlined in this blog out of the box.