Member post originally published in the Cerbos blog by James Walker

If you want to make your authorization more scalable, easier to maintain, and simpler to integrate with your components – externalized authorization is the way to go. However, these benefits are difficult to realize if you don’t consciously plan for them within your authorization implementation.

In reality, externalized authorization can add new technical challenges that aren’t always apparent at the start of a project. In this article, we explore some of the problems with externalized authorization. We’ll also go through several useful strategies to avoid these pitfalls, so you can implement authorization in the right way.

Authorization 101

Before getting into the concept of externalized authorization, let’s first start with the basics – bear with me.

The backbone of any secure application is authorization. It determines what actions a user can perform within an application. Authorization ensures users only access what they are allowed to. As applications scale, authorization often becomes more complex, especially when dealing with microservices or distributed systems. Spoiler alert – stay tuned for the upcoming release of our ebook on transitioning from monolith to microservices.

This is where externalized authorization comes in. But how exactly does it work, and what should you watch out for?

Externalized authorization – why and what?

Externalized authorization refers to separating your service’s authorization routines from your main application code.

In monolithic applications, authorization is handled by functions and classes that live inside your single codebase. Externalized authorization refers to repositioning these components as a standalone service that your main code interfaces with. The interface normally consists of network calls to an API that the authorization component provides.

Externalized authorization is often used together with external identity providers. You can delegate user account storage and role management to an authentication platform that’s purpose-built for the task. Then your application can pass this context onto the authorization layer to check whether the roles assigned to a user authorize an action or not with all the added context of the application.

This model keeps authorization logic separate from your application, making it more testable and easier to iterate upon in isolation. It also centralizes the implementation of authorization policies, ensuring all your services apply the same restrictions. Any new services you develop can reuse the externalized authorization component without duplicating its logic. This isn’t possible when authorization is tightly coupled to specific codebases. If you want to learn why companies are turning to externalized (decoupled) authorization more and more, check out this blog.

Complexities when implementing externalized authorization

Authorization logic that’s written directly into application components is inflexible, and externalized authorization has clear advantages over it. Externalizing authorization into its own service can increase overall complexity, though. You have to develop and maintain two services while ensuring they remain compatible with each other. Here are five specific kinds of technical complexity you’ll face.

Integration with the main app or service

Plugging the externalized authorization layer back into your main application can be harder than you think. Whereas authorization has historically been a synchronous process without side effects, externalizing introduces the potential for system failures when the authorization service can’t be reached or an unknown response is received.

The externalized authorization component must be carefully integrated to ensure the implementation is reliable. The application will need to retry calls to the authorization layer that fail due to a flaky network, for example. When no response can be obtained, perhaps because the authorization component has failed, the app should deny the user’s request to prevent authorization being inadvertently granted.

Each app you develop will need to integrate the authorization layer before it can be consumed. These integrations should be backed by tests that verify the app correctly handles different authorization outcomes, such as grant, deny, and failure.

Management of user accounts and permissions

Externalizing authorization from application code doesn’t mean you can forget about user accounts and permissions. These still need to be managed by your authentication layer, either directly within your account management component or with an external identity provider.

The service you use should be flexible enough to store all the user data you require. All but the simplest systems will require a granular permissions model with support for roles and groups. You’ll need a mechanism for setting up and maintaining user attributes and role assignments, either via scriptable APIs for automated provisioning or an accessible web UI for human use.

Externalizing authorization without planning how you’ll manage user accounts can cause problems as you reintegrate your components into your application. Apps need a dependable mechanism for establishing the user’s identity, retrieving relevant attributes such as their team and project, and checking which permissions have been assigned. All this info has to be centralized across your services to preserve consistency.

Handling data filtering and pagination

Services that filter data and display results over multiple pages need to be adjusted so users are only shown the items they can interact with. For example, an API request for the first ten invoices in a system could expose a different set of items depending on the user: department leaders might only see invoices approved by their department, while accounting staff have unfiltered access.

To achieve this, you’ll need to run your authorization policies against each item included in resource lists fetched by your application. The policies should verify that the items can be used in the current authorization context defined by the characteristics of the resource and the requesting user.

Unfortunately, performing authorization checks for lists of hundreds or thousands of records is often an inefficient process. It also has knock-on impacts on your pagination routines. When an item gets discarded because authorization is denied, a replacement must be loaded from the database to fill up the correct page size.

To properly address this complexity, plan how you’ll integrate your data queries with your externalized authorization policies. Select an authorization provider that includes features such as query plans to preemptively retrieve the list of resources that a given user can interact with. Use the plan to filter database queries at the point they’re made, instead of individually running authorization policies against each item in your result sets.

Maintaining security and privacy

Security weaknesses and privacy concerns can be caused by externalized authorization. Any new service increases your threat perimeter and creates an additional target for attackers. Splitting authorization out of your application converts it into a standalone component that might be easier for bad actors to manipulate.

Traditional authorization models are invisible from outside your application. Authorization checks occur within the code, providing no opportunities for attackers to investigate their logic. Externalized authorization can be more visible if your service isn’t properly protected. Network activity logs reveal the requests being made and the results obtained in response.

Insecure authorization APIs can leak data too. It’s vital to ensure your authorization service only responds to requests from known application services via a trusted service-to-service call. Otherwise, a rogue user or attacker could exfiltrate sensitive details by making direct calls to the authorization API.

Ensuring scalability and reliability

Authorization is a critical application component. It’s involved in almost every user interaction, demanding exceptional scalability and reliability. Poorly optimized authorization is a bottleneck that compromises your whole system’s performance.

Splitting authorization into its own service can increase latency as your apps have to wait for authorization checks to complete. Too many pending calls will increase congestion and lead to resource contention. If your authorization layer can’t scale with user activity, people will be left waiting at times of heavy usage.

Failure resilience is equally important. If authorization goes down, users won’t be able to log in or access functions that require permission checks. Authorization services should be deployed as multiple replicated instances to produce a fault tolerant architecture that can withstand individual instance crashes.

Implement externalized authorization the right way

Externalized authorization doesn’t have to be burdensome. You can mitigate complexity by sticking to proven strategies that promote an effective implementation. Building upon standard microservices patterns is a good starting point, but the following techniques offer specific best practices for splitting authorization from application code.

Use industry-standard protocols and frameworks

Whilst authentication is a known problem and standards such as OAuth2 and OpenID Connect have made these solution plugins and play, authorization is still in its early phases or standardization.

There are common best practices and approaches for RBAC, ABAC, and PBAC making use of the PDP/PIP/PEP model, and now there is an effort underway to define a standard for how all the components involved in the authorization ceremony interact.

The OpenID AuthZEN Working Group – of which Cerbos is a key member – is defining the specification to ensure adding fine-grained authorization is just as simple and interoperable.

Favor prebuilt authorization platforms and services

Cerbos comic strip

Much like building your own IdP, starting an authorization platform from scratch is a daunting task. You’re responsible for checking your authorization logic and maintaining security standards. Selecting a dedicated platform such as Cerbos gives you all the benefits of externalized authorization without the complexity.

These systems sit outside your stack and are integrated using their public APIs. You can register user accounts, handle logins, and set up authorization policies using RBACABAC and PBAC. They remove the complexity of inventing your own mechanisms for storing, evaluating, and querying authorization logic.

Seek specialized expertise and resources when building your own solutions

Some systems demand their own authorization layers either because of their sensitivity or due to legacy compatibility requirements. Developing your own authorization solution can be the only option in these circumstances, but you don’t have to do it on your own.

Minimizing features and keeping code paths lean is a good way to lessen security dangers and remove complexity. After distilling your solution to its essential requirements, you can more readily compare it to reference architectures or invite an external review. Seeking an audit and penetration test from a specialized authorization security team can provide confidence that your system’s protected, allowing you to get back to building your business functionality.

Start developing your own solution by clearly listing the vital features it requires. Next, plan out how you’ll deploy your authorization service, protect it from unauthorized access, and scale it to achieve sustained performance. You can then start working on the technical implementation. Try referring to open source authorization platforms if you need more guidance as many of the challenges you’ll face will have already been encountered by others.

Conclusion

Externalizing authorization from the code of individual applications is a best practice that enforces consistent authorization logic across services, simplifies testing, and is more scalable when implemented correctly.

Nonetheless, too many software teams struggle to effectively utilize externalized authorization because of the extra technical complexity it creates. Poorly planned implementations can be unreliable and difficult to maintain.

Proactively developing strategies to identify and address this complexity will let you build and scale a externalized authz system for your next project. Involve project managers, developers, and service operators to canvass opinion on potential drawbacks of the approach. Once you’ve identified any problems, you can add relevant mitigations to your development plan.

The measures you choose can be specific technical changes, such as implementing rate limiting to improve security, or more general steps that support your solution’s success. Extensive test suites, adoption of standardized protocols, and the use of expert guidance when needed all strip away the complexity of externalized authz.

Want more details on getting started with authorization? Set up a call with a Cerbos Engineer and ask us anything.