This is part 1 of a 2 part post. The second part can be found here

Introduction

I would hazard a guess that the majority of applications out there today use something along the lines of Role-based access control (RBAC) for authorisation purposes. What this usually means is an application has a list of roles (for example Admin, Owner, Contributor) that users are placed in to and then the code will check whether a user is in one of the roles that is allowed to undertake the current action.

if (!this.User.IsInRole("Admin")) {
	throw new ForbiddenException();
}

This works quite well for simple applications but falls apart quite quickly as the complexity of the application and/or the number of users increases. This can lead to “role explosion” as more and more roles are created which are very similar to others but differ slightly in the quest for fine-grained control e.g. Requirements like “We’d like Jane to have the same access as Dave but without X” lead to the creation of another role.

Another issue with (conventional) RBAC is that the authorisation framework knows nothing about the context in which the question is being asked - in our example code above we simply ask “Is the user in this role?”. RBAC can be extended to add some idea of context by, for example, adding extra features akin to row-level security that let you say “Is the user in this role and do they have access to subset Y?”.

To further complicate matters (and this is about to happen where I work), once you’re in a multi-tenanted situation you can end up with situations where one tenant would like role A to be configured in a slightly different way to another tenant. This either leads to more roles or roles with the same name doing different things!

We recently faced this type of situation at Polylytics with one of our clients. This client had a multitude of users all with quite varied access levels. We had an augmented RBAC system (as described above) that was creaking as it failed to provide the fine-grained access controls that the business required.

ABAC to the rescue!

A brief hunt around the literature surrounding access control throws up a whole bunch of acronyms and models that all seem quite similar and very simple to grasp. I imagine that in a few days, a group of experienced developers could re-invent pretty much all of the models that you’ll find on Wikipedia. As with all things auth-related, though, the key is in how to implement them securely and in a manner that doesn’t excessively affect performance.

To me Attribute-based access control (ABAC) pretty much ticks all the boxes required to build a contextually-aware dynamic authorisation system. I’ll let Wikipedia introduce ABAC:

Attribute-based access control (ABAC), also known as policy-based access control, defines an access control paradigm whereby access rights are granted to users through the use of policies which combine attributes together. The policies can use any type of attributes (user attributes, resource attributes, object, environment attributes etc.).

Unlike role-based access control (RBAC), which employs pre-defined roles that carry a specific set of privileges associated with them and to which subjects are assigned, the key difference with ABAC is the concept of policies that express a complex Boolean rule set that can evaluate many different attributes.

Although the concept itself existed for many years, ABAC is considered a “next generation” authorization model because it provides dynamic, context-aware and risk-intelligent access control to resources allowing access control policies that include specific attributes from many different information systems to be defined to resolve an authorization and achieve an efficient regulatory compliance, allowing enterprises flexibility in their implementations based on their existing infrastructures.

As you can see, this sounds exactly like the sort of thing we need to create our dynamic, contextually-aware authorisation system. The key thing to take away from the above description is that ABAC lets you ask the following sort of questions: “Can this user do X to Y given Z”. The X here is usually referred to as the “Action”, the Y is the “Resource”, the Z is the “Environment” that you’re in and obviously the user has a whole bunch of properties about them as well.

The problem, at least in the .Net world, is a complete lack of implementations available to use.

Our implementation

At Polylytics we’ve implemented our own .Net specific ABAC library. There’s a core part of the library that provides the runtime set of components and then a higher level set of components (and UI) for creating and managing policies at runtime through a DSL.

The core components

You can use the core set of components on their own and either hard-code the policies (as we’ve done in several projects) or provide your own mechanism for generating policies at runtime. The core functionality is actually quite simple and revolves around the core interface: IAuthorizationService.

public interface IAuthorizationService {
	Task EnsureAsync<TAction>(TAction action);

	Task EnsureAsync<TAction, TResource>(TAction action, TResource resource);

	Task<AuthorizationResponse> CanUserAsync<TAction>(TAction action);

	Task<AuthorizationResponse> CanUserAsync<TAction, TResource>(TAction action, TResource resource);

	AuthorizationBatchBuilder Batch();

	Task<Expression<Func<TResource, bool>>> GetPredicateExpressionAsync<TAction, TResource>(TAction action);
}

Hopefully this interface is fairly self explanatory. The two EnsureAsync methods ask “Check the current user can do action” and “Check the current user can do action to resource” respectively. The CanUserAsync methods do the same but return an authorisation response to inspect instead of simply throwing an exception as the EnsureAsync methods do. Batch allows you to construct a batch of questions to ask the authorisation service. In hindsight, this is clearly a bit of a leaky abstraction and doesn’t belong here but this allows you to ask many questions at once on the assumption that the implementation can be quicker in this mode (as we’re sometimes sending these over Http that’s definitely the case and why it exists). The GetPredicateExpressionAsync method is a bit different but allows you to ask the inverse question, which is “Tell me the list of resources that this user can perform some action on”. Clearly, that’s not quite true as it returns an Expression<Func<TResource, bool>> so what it actually does is return a predicate that can be then passed in to a LINQ query or an ORM that supports applying where clauses of this type. At Polylytics we use our own ORM (Dashing) that does indeed accept predicates of this form.

One of the things to notice with the IAuthorizationService interface is the lack of reference to the logged in user and the environment attributes that are part of ABAC. In the default implementation of IAuthorizationService the user and the environment attributes are provided via a UserProvider and EnvironmentProvider.

The other side to this is then how to we specify policies for the framework to use? This is handled by the following interface:

public interface IPolicy<TUser, TEnvironment> {
	/// <summary>
	///     Indicates whether this policy applies to the authorization context.
	/// </summary>
	/// <param name="context">The authorization context which contains the request and the user</param>
	/// <returns>A boolean indicating if this policy can handle the context</returns>
	/// <remarks>This method should execute quickly, hence the non-async nature.</remarks>
	bool CanAuthorize(AuthorizationContext<TUser, TEnvironment> context);

	/// <summary>
	///     Performs authorization for the pass in context.
	///     To indicate that the authorization succeeded call context.Succeed();
	///     A policy does not need to fail generally as other policies may succeed (creating an OR type situation).
	///     However, you can call context.Fail() to guarantee failure irrespective of whether other policies succeed.
	/// </summary>
	/// <param name="context"></param>
	/// <returns></returns>
	Task AuthorizeAsync(HandleAuthorizationContext<TUser, TEnvironment> context);

	/// <summary>
	///     Provides a lambda expression that can be applied to instances of TResource within the AuthorizationContext
	///     in order to apply authorization based filtering in line with this policy.
	///     To indicate that this policy should provide a filter call context.Succeed() with the predicate
	/// </summary>
	/// <param name="context"></param>
	/// <returns></returns>
	Task GenerateFilterAsync<TResource>(FilterAuthorizationContext<TResource, TUser, TEnvironment> context);
}

Hopefully, this is fairly simple to follow but a look at the core bit of the framework casts a little more light on how this gets called:

foreach (var policy in this.manager.GetPolicies()) {
	if (policy.CanAuthorize(handleAuthorizationContext)) {
		await policy.AuthorizeAsync(handleAuthorizationContext);
	}
}

So, you can see that the core logic is actually very simple: loop through all the policies and call them one by one. CanAuthorize should return very fast (which is why it’s not async) so that only necessary work in AuthorizeAsync is done.

The rest of the core is concerned with setting up the contexts, providing in-memory and http clients and then making it go as fast as possible through caching. I’d like to mention a particular library at this point for serialization in .Net, as it’s both very performant and an absolute joy to work with: Hyperion.

In part 2, coming soon, I’ll talk about the management of policies through a web-based policy editor using a DSL.