ASP.NET Core: Supporting multiple Authorization, route branching

It’s been six months since my last article, my freelance career is asking a lot of effort.

I've been experimenting with multiple authorization once again, but this time complete separation was a requirement. The application needed to be branched, in fact. This ensures authentication isolation. Another requirement arose when we wanted to host specific controllers dedicated to a authentication method, all within one ASP.NET Core host process.

The simplest solution would be to host two separate applications in Azure and to have an NGINX or any other proxy route the specific routes to their destined applications, including the authorization. But I thought it would be neat to experiment with something I read a long time ago, called Pipeline Branching.

I’m a fan of Filip’s work, his development is available through the Nuget package WebApiContrib.Core which includes a lot of handy features, one of my favorites is Controller-less Razor Pages.

I’ve wrote about supporting multiple authorization in the past, this is yet another technique to enforce two kinds of authentication in one ASP.NET Core WebAPI.

What we will achieve in this article

For the demo, we’ll continue on the custom MyDb authentication we described in the previous article: “Adding a custom authentication scheme”

These basics can be applied to any form of authentication, for simplification, I chose not to bother with Azure AD authentication and instead use a custom database and a custom authentication scheme. This way, you should be able to run it locally without depending on Azure AD. All you’ll need is a local SQL instance, I suggest using Docker :

The diagram below showcases the branching that will be done in one ASP.NET Core webapplication hosted on port 5000. In fact, the root (:5000) will have little to no middleware configured.

The actual implementation resides in the branches, as show here:

Each branch will have its own MVC pipeline and its own authentication mechanism. They both share the same codebase, but different configuration.

What is the benefit of this?

The benefit of this setup is that both branches (/api1 and /api2) receive their own IOC container and therefore each branch can handle their own kind of authentication. The use cases for this can vary, you could handle multiple tenants using route branching, multiple environments with different implementation. You could, perhaps, build your own API gateway using this technique.

Let’s see how this is done effectively, let’s start by adding the controllers:

We want both controllers to be authorized using the same Authorization Policy (MyDbPolicy) that only requires an authenticated user.

However, we’ll need to support two ways of authenticating, either using the MyDb1 authentication database or using the MyDb2 database.

Please note that these can be two entirely different authentication mechanisms, I’m omitting a lot of plumbing by simple authenticating against a database in plain text, I hope you get the idea.

To solve this issue, I suggest to branch the application into two branches, one with MyDb1 authentication and the other with MyDb2 authentication, both using the same authorization policy, as a result, the API’s will authenticate against their own predefined database, but executing the same business logic code. This way, the logic will be unaware of the authentication mechanism. All that’s required is any authenticated user to run the controller logic.

To setup ASP.NET Core pipeline branching, you’ll need to install the WebApiContrib.Core nuget package first:

After the installation, we’ll split the pipeline using the UseBranchWithServices extension method from the package, our Startup file should look like this:

Since we’re not doing anything related to MVC in the ConfigureServices and Configure methods, the application will respond with “Branches configured!” when browsing to the root /.

This makes sense, since the pipeline has not been configured to use MVC, it is only in the branches that MVC is setup.

A fully working solution is available through a GitHub branch.

After cloning this repo, please check out branch origin/route-branching-separation to see the fully working solution.

git checkout -b route-branching-separation origin/route-branching-separation

Before running the repo, make sure to have two local databases running first (MyDb1 and MyDb2). Run the SQL\create_auth_table.sql script for each database to ensure the Authentication table on both databases.

After the application is up and running, open postman to test both API’s.

These are the results for /api1:

The following are the results for /api2:

As you see, two different routes using different authentication methods. The pipeline split into two entirely different branches.

We could go even further and separate the controllers entirely to only be visible to a specific branch.

The diagram below describes what we will achieve next, the /api1 branch will host the users API, the /api2 branch will host the data API. Both branches will be hosting the common API. But only the branch-specific API’s will be exposed by their respected branch. Navigating to /api2/users will result in a 404.

To achieve this we need to have a way to annotate the API’s so that we can specify to what branch they belong. Also, we need a way to discover those API’s.

The RouteBranchAttribute will be added to a branch-specific API, to categorize the API.

With the Users API associated to branch /api1 and the Data API associated to branch /api2, we can now introduce the Common API that is not associated to a specific branch, this will be available to both branches.

The Application Parts API from ASP.NET Core

Filip W has a great article that leverages this feature, regarding generating generic controllers.

We’ll be implementing a ControllerFeatureProvider to help us include/exclude API’s that are not associated to our branch.

At first, we’ll check if the type that is visited by the FeatureProvider is a ControllerBase (WebAPI Controller), secondly a check is done to verify if the controller contains the RouteBranchAttribute, if not, it’s a common controller and should be added to the pipeline.

If the attribute is present, we check if the route maches the route for the RouteBranchControllerFeatureProvider, if so, the controller is associated with this branch.

The startup file reflects the intention to add controllers associated to the branches.

In the code above we see the application parts being configured by adding our ControllerFeatureProvider and the current Assembly to the application builder.

In postman, we can now test /api1, which should expose the Users and Common API’s using the MyDb1 Authentication scheme.

The Data API should result in a 404, since it is not loaded for this branch:

The same goes for branch /api2, it should expose the Data and Common API’s, while returning 404 for the Users API, using the MyDb2 authentication scheme.

Request is HTTP, <scheme> Authentication will not respond.

In summary

Freelance Software Engineer