Agilix
Published in

Agilix

ASP.NET Core: Supporting multiple Authorization

Every once in a while, you get the requirement to support multiple ways of authenticating within one application. This article covers the rare case of supporting two authentication providers from within the same ASP.NET Core WebAPI.

The example setup is the following: For routes starting with ‘/api/users’, we want to authenticate the current user against an Azure Active Directory, the routes ‘api/data’ we’ll want to do our own authentication against a table in a MS SQL database.

Setting up Azure Active Directory authentication is quite effortless, the real complication is in supporting more ways of authentication. This is done via Authentication Schemes and Policies.

To set up a single authentication and authorization configuration, the AddAuthentication and AddAuthorization extension methods will have you sorted quite rapidly. This will configure the Bearer schema, but you can’t configure multiple schemes with the same name, so let’s look into that.

TL;DR;

Check out the finished master branch on GitHub.

Let’s create a table in MS SQL first, this table will hold our authentication data in plain text, this is not advised to do in production and is solely for sake of demonstration. The database is called MyDb and it’s hosted on a local SQL Express server. Here’s the CREATE TABLE script.

Simple enough, this also includes a sample username and password.

Next, we need an Azure Active Directory Application ID and domain, if you need to get started on how to create that, have a look at an earlier post of mine.

Now, scaffold a new ASP.NET Core application using the .net core cli.

mkdir auth.api
cd auth.api
dotnet new webapi

In the Controllers folder, delete the scaffolded ValuesController and create two new ones.

DataController.cs
UsersController.cs

Both Controllers return the user’s identity as a result, depending on the authentication scheme, the value of HttpContext.User.Identity.Name will differ.

Azure AD Authentication

To support Bearer authentication using Azure AD, we’ll add a policy to the authorization pipeline and set the default authentication scheme and default challenge scheme to “Bearer”.

I’ve written a small wrapper function to do this.

To use this, add the following line to your Startup.cs file in the ConfigureServices method:

services.AddAzureAdAuthorization(Configuration);
// AddMvc must come after the authentication configuration
services.AddMvc();

Add this line in the Startup.cs file in the Configure method:

app.UseAuthentication(); // ==> Do this BEFORE calling app.UseMvc();

The only that rests is to add our settings in the appsettings.json file and make our ApiController use Authorization.

"Authentication": {     "Authority": "https://login.microsoftonline.com/{0}",     "Tenant": "<DOMAIN, like: merkenmaartengmail.onmicrosoft.com>",     "ClientId": "<Application ID, GUID>"
}

Add the Authorize attribute to the UsersController.

Authenticating using Azure AD

To test our authentication on ‘api/users’ we need a valid Bearer token. Since Azure AD does not allow username:password flow of acquiring such a token, you’ll actually need to have a concrete client implementation in order to generate a token. Please look into a previous post of mine to generate a token for your domain and Application Id using an Angular client.

To test our authentication, we’ll use Postman.

Once you got the token, paste it into the Token field of the Authentication tab under type “Bearer”. Finally, execute the GET request to ‘api/users’ using this token. The result should be an array of 2 items where the second is your username.

You can see this in action on the single-auth branch of the companioned repository.

Another approach to setting this up

The app.UseAuthentication() helper method adds the AuthenticationMiddleware to the ASP.NET Core pipeline, this enforces authentication where the AuthorizeAttribute is used. When applying a custom authentication scheme (other than Bearer), you’re configuring authentication yourself. So this call becomes redundant.

Modify the AzureAdExtensions.cs file to use the custom Constants.AzureAdScheme.

Modify the UsersController.cs to use a specific authentication scheme (Constants.AzureAdScheme).

Finally, remove the UseAuthentication() line from the Configure method in the Startup.cs file.

a̶p̶p̶.̶U̶s̶e̶A̶u̶t̶h̶e̶n̶t̶i̶c̶a̶t̶i̶o̶n̶(̶)̶;̶

The UsersController should still work as before, now we’ve prepared this setup for adding another custom authentication method, by means of looking up a user in a custom database.

See the results in the companioned branch.

Using a custom Authorization Filter

Although using the AuthorizeAttribute is a neat way of stating your intent, sometimes you need to have a little more control over what actually happens during authentication, or you want to do the authentication yourself. Therefore you could write your own Authorization Attribute.

The use-cases are plenty. For example, you could run your own business logic during authentication and fail the result in case something is not in order. You could log the authentication into a custom database, like auditing, for example.

If you’d like to combine Azure AD with a custom authentication (or authorization) method, this is where you’d check the third party identity provider for existence of the logged in user.

Anyway, here’s an example of an AuthorizeFilter.

And an excerpt from the logs.

You can check this out on the companioned branch.

Adding a custom authentication scheme

Now we’ll enter the territory of actually authenticating the user ourselves.

This example is heavily based on the idunno.Authentication codebase by .NET Security person, Barry Dorrans, I’ve made some tweaks in order to support both Azure AD and a custom authentication method side by side, to demonstrate different authentication schemes. I recommend taking a look at the original repository first.

We’ll call the custom scheme ‘MyDb’ because we’ll authenticate using our own Authentication table in our own database.

The Authorization challenge needs to be provided via the MyDb scheme in the form of Username:Password in plain text. This is solely for sake of demonstration, please don’t ever send user data in plain text, even if it’s over HTTPS.

First, we’ll need a service we can leverage in order to check if a user is authenticated based on username and password.

The above service should be quite self-explanatory, it basically does a query to the Authentication table to check whether the username:password combination exists.

And inject that service as transient.

services.AddTransient<ICustomAuthenticationService, MyDbAuthenticationService>();

A lot of boilerplate goes into creating a custom AuthenticationHandler. Let’s go over this diagram to get a better understanding of what makes this AuthenticationHandler tick.

The hander has two main extension points, HandleChallenge and HandleAuthentication. The challenge handler will be called when the Authorize HTTP Header was not provided, in our case we’ll return a 401 or a 500 in case HTTP was used instead of HTTPS.

The authentication handler is where we’ll handle the parsing of the Authorize HTTP Header, create a validation context (wrapper object) and emit an event with the provided context and the ASP.NET Core serviceprovider as properties. This allows our custom code to leverage registered services in order to do the authentication. After validation, a ticket is created with the authentication data. In case of error, a 401 is returned.

The Authentication Event will be called during HandleAuthentication, this allows us to hook into the middle of the authentication flow. By registering it as an event and providing it in the AuthenticationSchemeOptions for the AuthenticationHandler, we’re able to run custom authentication code as a delegate in the AddScheme extension method.

Because we’re provided with the ServiceProvider, we can request an instance of our ICustomAuthenticationService and authenticate our user based on username and password.

With this in place, you just need to add the Authorize attribute to the DataController with our MyDb scheme.

If we now request data from the ‘api/data’ endpoint, we get prompted to authorize using the MyDb scheme.

By doing just that, we get the authorized data.

The full solution can be found in the companioned branch.

Dynamically applying an Authorization method at runtime

Up until now, we’ve managed to secure our endpoints (api/users, api/data) using Azure AD Authentication and a custom authentication handler declaratively by adding the AuthorizeAttribute at class-level of the ApiController.

Adding global authentication

In some situations, you’ll want all of your controllers to use a specific authentication mechanism, to add this globally, you’ll need to add it to the pipeline at configuration time.

This, however, adds the Azure AD authentication to all our controllers, including the DataController, which we wanted to secure using our own custom authentication mechanism. We’ll handle this issue next.

A working implementation can be found at the companioned branch.

Applying authentication filter at runtime

Let’s say, instead of our two controllers (api/users and api/data) we’d want to secure the /api route with Azure AD and all other routes with our custom authentication mechanism. The current setup does not support this. In order to apply an AuthorizeFilter at runtime would mean that we’d need to modify the ASP.NET Core pipeline during a request.

This can be done using an IFilterProvider.

The FilterProvider is given a list of empty filter slots that is needs to provide filters for. In our case, we’ll leave it to the default implementation to take care of that, we’re just interested in adding an AuthorizeFilter to the Filters list.

This working example can be found on the companioned branch.

Wrapping up

We’ve covered a lot in this article, ASP.NET Core authentication is a broad subject. Hopefully this should give you more insight on how to write custom authentication logic for your business applications.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store