In the previous blog, A Tour of IAM Standards, I walked through a history of standards relating to IAM (Identity and Access Management). OAuth 2.0 provides a great building block for identity and access management solutions and its inclusion of scopes provides a starting point for authorisation. However it’s just that – a starting point. OAuth 2.0 scopes are useful for determining which features a user has access to but are unsuitable for anything more complex such as which individual resources a user may access.
The lack of standardisation for authorisation results in a proliferation of bespoke solutions. Security vulnerabilities and lack of maintainability are often the result. In this blog we’ll explore a generic approach for modelling entitlements that has been applied in multiple business contexts, has been proven in real-world solutions and provides a flexible starting point without undue up front effort.
A Spectrum of Complexity
Designing authorisation solutions is difficult. It’s a cross cutting concern in that most services have to address it, but once you dig into the details each business domain introduces its own complexities. This is particularly challenging in a distributed service-based environment where the functionality is spread across many components and teams. A clean separation of responsibilities is dependent on identifying good boundaries.
We have to decide how much functionality to centralise and how much to leave up to the individual business domain. There are competing tensions at play:
- Implement within each business domain – This is much simpler to design. However, it results in duplication of effort, makes it difficult to introduce new security features to all services, and increases the chance of security vulnerabilities.
- Implement centrally – The design complexity of a central solution is higher. It tends to require more effort for an MVP solution. Its knowledge of business domains results in coupling that can make it more difficult to evolve individual business services.
I’m going to describe a middle path. It won’t solve all problems, but I’ve found that it’s a good place to start if you wish to avoid painting yourself into a corner without requiring enormous effort to implement. This will centralise functionality that is common to most services and leave the rest up to the individual services to implement with clean separation of responsibilities.
But before we do that, we need to define an authorisation API.
A Generic Authorisation API
The term “fine-grained authorisation” will be used here to describe the checks implemented for determining whether actors are allowed to access or modify individual data records. This contrasts to coarse-grained authorisation such as OAuth scope checks, that can be used to restrict access to solution modules or functionality. API-based solutions typically expose data as API resources. An authorisation API will often be given three pieces of information and asked if the requested operation is allowed to proceed.
- The actor – The user/system making the request.
- The operation – The action being performed.
- The resource – The piece of data being operated on.
One problem with this approach is that it requires the authorisation API to have knowledge of every single resource/entity in the solution. Centralising this API requires a lot of plumbing to make it aware of all entity updates. It is very sensitive to race conditions due to the short time intervals between resources being created/modified and subsequently accessed.
I’m going to make a tweak to this API to relax these constraints but first I need to introduce a new concept.
Party Based Modelling
The distinguishing feature of Party Based Modelling is that it defines authorisation rules in terms of who is allowed to act on behalf of somebody else. This differs from many other systems that define who is allowed to act on specific resources. Let’s explore what that means.
A key concept in authorisation queries is ownership. Knowing who or what owns a resource is usually a critical piece of information. Modelling ownership is often complicated.
For simple systems it may be possible to equate ownership with identity. In other words, concepts like identity, user, customer, etc. may all be one and the same. A customer signs up as a user with an identity, they create some data which they now own, and they’re the only one who is allowed to access it. Authorisation checks in this scenario are very simple and just need to check if the logged in user matches the owner of the resource.
Unfortunately most real world systems are more complicated than this. Two complicating factors are:
- Not all resource owners have identity. An example of this is an organisation unit. It is not an individual and can’t log in in its own right.
- Actors may often access data that is owned by somebody or something else. A common example is a person who works on behalf of an organisation or a system service.
We want to be able to represent these types generically and allow new types to be added over time. Let’s introduce the concept of a Party. A Party is an abstract type from which all Actors and Owners can inherit.
This Venn diagram shows how actors and owners and example concrete types are represented by the Party concept.
We shouldn’t assume that our customer modelling can be distilled down to persons/individuals and organisations. This may seem like an obvious place to start but hard-coding these two types leads to messy workarounds being developed later. To give some examples, these are some parties that may own or act on resources that I’ve seen in the real world.
- Persons – Individual users who interact with the solution.
- Organisation – Users will often work within and on behalf of a business.
- Organisation unit – Any non-trivial business will typically be made up of hierarchies that need to be understood and modelled.
- Third party vendor – Sometimes customers will make use of third party vendors (e.g. a SaaS provider) that make calls on their behalf.
- Franchise – Sometimes a business will have a relationship with another business where that other business can access some restricted view of their information (e.g. aggregated business reporting).
- Data aggregator – Sometimes a business will package up and sell aggregated data sets and market insights.
- Regulator – Some industries have government regulators that ensure businesses remain compliant with the relevant legislation. They may be given access to reports and other information that allows them to perform this role.
- Family – Retail solutions may wish to allow family members to pool resources and share benefits.
- Service – There will often be system actors in the solution that act on data not owned by them.
By using the concept of a Party, our entitlements solution can treat them all consistently. These Party types may own resources and/or they may act on resources. Crucially, they may act on resources owned by another Party. We can model what each Party is allowed to do on behalf of other Parties by introducing the concept of a Grant. Party and Grant provide an authorisation abstraction that can support complex permission relationships without knowledge of complex real-world types and hierarchies.
A Clean Boundary of Responsibility
We’ve talked about a few types up to this point. We now have:
- Identity – Information allowing an actor to be uniquely authenticated as well as coarse-grained permissions in the form of Scopes.
- Resource – A type of data exposed by the API of the system.
- Party – An abstract type that is the parent of all owners and actors (e.g. person and organisation). Using object oriented terminology, a person “is a” Party and an organisation “is a” Party.
- Grant – A definition of what operations a Party can perform on behalf of another Party.
Conceptually the following relationships are used to perform authorisation checks.
- The actor is identified using Identity.
- The Identity is associated with a Party that represents the actor.
- Each actor has a set of Grants that define the set of owner Parties that the actor Party can act on behalf of.
- The owner Parties own resources that can be operated on.
We can now describe a way to model this in a microservices environment with clean boundaries of responsibility. Let’s look at a typical architecture.
The components listed here are:
- User Client – This is the web or mobile application exposed to the end user.
- Identity – This is a system that implements the OAuth 2.0 and Open ID Connect specifications. It is used to authenticate the end user. It may be used to authenticate system actors. The initial sign-up process is also typically implemented or at least supported by the Identity solution.
- Gateway – An API gateway that routes incoming requests, validates access tokens, and performs coarse grained authorisation checks based on scopes. Many other features such as rate limiting, web application firewalls, observability, etc. are also required but not discussed here.
- Customer Service – Responsible for managing information about customers. This will typically expose self service features such as managing user profiles and organisation structures. In the real world it is rarely encapsulated into a single component but will be described as such for simplicity.
- Business Service – This represents the various business domain services that provide the functionality offered by the solution. There will be many of these.
- Entitlements – This encapsulates the common functionality for fine-grained authorisation that we want to expose to all business services. There is typically some synchronisation and/or consistency between fine-grained entitlements and coarse-grained scopes assigned to users.
Let’s now map responsibility for each of our types onto these components:
- Identity – This is managed by the Identity component. Identity records should strive to be minimal and not duplicate all customer data.
- Resource – All business entities are managed within the various business domain services. Each resource has an owning Party which can be of any customer type.
- Party – The Entitlements component manages all Party records. These are a projection of data mastered by the Customer component. We’ll touch on this more when we dig into data relationships. The key point is that we want to allow Customer modelling to evolve without impacting the authorisation-specific abstraction provided to business domains.
- Grant – The Entitlements component manages all Grant records. Similar to Party, these are a projection of relationships between customer types in the Customer component. Where a Customer understands the specific types of Parties and their unique relationships, a Grant is concerned only with what one Party can do on behalf of another Party. It has no knowledge of customer types or hierarchy, only permissions.
The division of responsibilities is clean. From a data perspective each component is responsible for:
- Identity – Identity record with credentials allowing a user to login.
- Customer Service – Customer records and their relationships. Very business specific.
- Entitlements – Abstracted Party records and Grants that capture what operations each Party can perform on behalf of another.
- Business Services – Business domain specific records and their relationship to a generic Party owner.
No knowledge of individual resources is visible to any IAM related components. No understanding of customer modelling is required by business components. There may be exceptions to this but from experience they can be dealt with on a case by case basis.
Real World Data Modelling
It can be difficult to understand the distinction between customer modelling and permission modelling so let’s describe a concrete example.
In this simplistic example we’re assuming that our customers can be modelled as follows:
- Person – An individual user who interacts with the solution.
- Organisation – Individuals work on behalf of organisations.
- Employee – Represents a relationship between a Person and an Organisation. Note that a Person may work for multiple Organisations.
Let’s now look at how these are projected into our Entitlements component.
- Each Person and Organisation maps into a Party record. We have introduced the concept of Source to record the origin of each record, this enables subsequent synchronisation to be performed in an incremental fashion.
- All records mastered in the Entitlements component such as Source and Scope have a “code” field which is a stable identifier that can be referred to in code. For example, a synchronisation job keeping Party records up to date with Person changes can discriminate by a Source type with code=person.
- Each Employee maps into a Grant record allowing that Person to act on behalf of their employing Organisation. Similar to Party, Grant is associated with a Source to assist with synchronisation. More complex modelling might involve different types of employees mapping to different types of Grant with varying privileges.
- We’ve introduced a Role entity. This allows a set of Scopes to be grouped together and allows a single Grant to represent many permissions. It also allows the set of Scopes associated with a type of Grant to be evolved over time without updating the individual Grant records themselves.
- The Role, Scope and Source entities are effectively configuration data and updated during deployments.
- The Party and Grant entities are projections however the id field is generated. This gives us a stable identifier format that is independent of the source system. Source systems are often legacy based and we don’t have control over the identifier they use. We maintain a mapping back to the original source system entity with a sourceId attribute.
Key callouts:
- Parties and their relationships are modelled in the Entitlements component. It is not necessary for business services to understand customer relationships. This allows new customer relationships and even party types to be introduced over time with much reduced likelihood of impact to services. In other words, we reduce coupling between business domains and customer modelling.
- Parties may have an associated Identity. Several types of Party will have identity (e.g. end user and system) but many others (e.g. organisation) will not. It’s possible (but not ideal) for a Party to have multiple forms of Identity which can be useful when migrating between identity platforms.
- The Entitlements component doesn’t have knowledge of individual resources. It may have implicit knowledge of resource types in its Scope definitions but these will often be grouped at a higher level for permission purposes (i.e. use OAuth 2.0 scopes modelling as a guide here).
- For this example, the scopes on the Identity record are assumed to be identical to the scopes assigned to the corresponding Person and would therefore be kept in sync when permissions are modified. More complex solutions may choose to create a mapping between the two and have coarser grained scopes.
- Because all business domains depend on the Entitlements component, it is critical to the availability of the solution. Paring it back to a minimal set of functionality allows it to be carefully tested and optimised. Additionally, more complex deployment models distributing the data and compute closer to business components can be explored.
A Party-based Authorisation API
We can now return to our Authorisation API and make changes that simplify the implementation. Because the Entitlements service doesn’t know about individual resources it cannot implement the Authorisation API as we described it earlier. Instead of accepting resource ID as an argument, the owning party of the resource must be supplied instead. So we now have:
- The actor – The party making the request.
- The operation – The action that is to be performed on the resource.
- The owner – The party who owns the resource.
The Entitlements component understands the relationships between an actor Party and an owning Party via Grants and Roles and can support this API. This change results in clean separation between the responsibilities of a business domain and an entitlements service. The business domain is responsible for modelling resource-to-owner relationships and the entitlement service is responsible for party-to-party relationships.
How business domains determine the owner of an individual resource is up to the implementation. An owner attribute may be modelled on individual resources or at any point in a resource hierarchy depending on what makes sense for that business context.
We now have a solution that can support all party types without knowing them all in advance, it is data driven. This requires far less plumbing than a fully centralised model because resource types (or more typically scopes) can be registered with the entitlements service in advance but resource IDs remain known only to each individual business service. Integration is limited to components managing parties and relationships such as user registration and onboarding. Authorisation related race conditions are avoided for all resource modifications because they are not visible outside the individual business domains. Race conditions are restricted to changes in the Customer component, use cases here are usually more tolerant of short delays.
This is a much simpler model to implement than fully centralised. So, what are we giving up? The main limitation is that every single resource must have a single owner. It is not possible to attach an ACL (access control list) to individual resources. Allowing multiple Parties to access a single resource is possible by using a Party type that has members (e.g. an organisation) and associated Grants between the owning Party and actor Parties. This works well when relationship types can be defined in advance. Less flexibility is provided to end users.
It does provide a good stepping stone to a more complex model if that is required down the track. Implementing a party based model from day one and properly modelling your various customer types helps avoid most of the tactical short cuts that make ongoing enhancements painful.
Conclusion
Implementing a centralised entitlements service that models party relationships and avoids knowledge of individual resources provides a solid foundation for solving complex authorisation requirements. It has some limitations but can provide a good tradeoff between the need to release features quickly to market and avoiding the need for major rework later. It allows customer modelling and security functionality to evolve independently of individual business domains.
In the next blog we’ll start exploring the implications of a fully centralised model and the Google Zanzibar whitepaper.