Microsoft Identity Claims in ASP.NET Core: OperationAuthorization Requirement

I didn't know this existed until a reader made me aware of it. Today, we update the menu systems with Microsoft Identity using the OperationAuthorizationRequirement

Written by Jonathan "JD" Danylko • Last Updated: • Develop •

Padlock with key surrounded by chains

Back in December, I demonstrated a way to use granular authorizations in an application using Microsoft Identity with claims. This built upon the integration of a menu system and Microsoft Identity. 

While I built this originally for .NET Framework 4.7.2, I was made aware by a reader (Thanks, Wesley!) where this already existed in ASP.NET Core.

This was another time where I thought this concept through and created something similar before I ever knew it was in ASP.NET Core (I swear I never knew about it).

While I'm over the moon because it's very similar to what I implemented, ASP.NET Core called it something different: the OperationAuthorizationRequirement class.

Transition from .NET Framework to Core

After reading through the documentation for resource-based authorization, I realized this was exactly what I implemented for the .NET Framework.

While I won't rehash what was already discussed in detail in the Granular Authorizations Series, I will say it was easy to implement the PermissionFlags as OperationAuthorizationRequirement (example) into ASP.NET Core, but I still needed the PermissionFlags to identify the number for permissions.

Another change was using the IAuthorizationService for my resources throughout the system.

Remember the PermissionManager with the IsAllowed() method we created?

We would access the claims, find the resource ID in our list, and examine the flag to identify what they have access to in the system.

With the new resource-based authorization, we now rely on the IAuthorizationService for our granular authorizations using (you guessed it) the Microsoft Identity Claims. The data can stay the same, but implemented differently for .NET Core.

Implementing the IAuthorizationService

The AuthorizationService gives us a single-point of custom authorization throughout applications. It's automatically included with the Microsoft Identity package and we can dependency inject it throughout the system.

If there is an authorization handler class, it'll be called through the AuthorizationService. After creating your AuthorizationHandler, make sure it's dependency injected as well.  

 services.AddSingleton<IAuthorizationHandler, MyAuthorizationHandler>();

Pretty simple, but what does the MyAuthorizationHandler look like?

public class MyAuthorizationHandler 
    : AuthorizationHandler<OperationAuthorizationRequirement, string>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, 
        OperationAuthorizationRequirement requirement,
        string resourceId)
    {
        var claim = context.User.Claims.FirstOrDefault(e => e.Type == resourceId);
        if (claim == null)
        {
            return Task.CompletedTask;
        }

       var userPermission = claim.Value.ToInt();

       var value = (PermissionEnum)Enum.ToObject(typeof(PermissionEnum), userPermission);
        var permission = requirement.Name.ToEnum<PermissionEnum>();

       var success = value.HasFlag(permission);
        if (success)
        {
            context.Succeed(requirement);
        }

       return Task.CompletedTask;
    }
}

It's pretty similar to the IsAllowed() method from the PermissionManager. We're just moving the authorizations out of the PermissionManager and into our AuthorizationHandler.

Let's walk through this method.

The method signature requires a context, requirement, and, in our case, a resource ID. For our purposes, a resourceId is our menu id from the database.

When a user logs in using Microsoft Identity, it loads the profile of a user. This includes the roles and claims for that user. As you move from page to page, we always have those roles and claims available to us making it easy for us to examine authorizations.

We look through the user claims and try to find the menu id in the Claims type. If it can't find it, return to the caller meaning they don't have access to this resource based on the operation requested (the OperationAuthorizationRequirement).

As we continue on, we find the PermissionFlag represented as the Claim.Value and convert it into an integer.

We take the integer and convert it into a PermissionEnum. Our OperationAuthorizationRequirement is then converted into a PermissionEnum and we check to see if the user is authorized to perform the requirement.

If they are, we tell the context we succeeded and mark the task as complete.

Using OperationAuthorizationRequirement

Our OperationAuthorizationRequirements should mirror our PermissionEnum functions for our application. In our case, we have Create, Update, View, Delete, Upload, and Authorize. 

public static class UserPermission
{
    public static OperationAuthorizationRequirement Create = new() { Name = ((int)PermissionEnum.Create).ToString() };
    public static OperationAuthorizationRequirement Update = new() { Name = ((int)PermissionEnum.Update).ToString() };
    public static OperationAuthorizationRequirement Read   = new() { Name = ((int)PermissionEnum.View).ToString() };
    public static OperationAuthorizationRequirement Delete = new() { Name = ((int)PermissionEnum.Delete).ToString() };
    public static OperationAuthorizationRequirement Upload = new() { Name = ((int)PermissionEnum.Upload).ToString() };
    public static OperationAuthorizationRequirement Authorize = new() { Name = ((int)PermissionEnum.Authorize).ToString() };
}

These should stay close together since the OperationAuthorizationRequirement wraps around each PermissionEnum.

Since we need the number of the operation, we'll use the number as our name. The number represents the flag of the operation we want to authorize.

When it's received by the AuthorizationHandler, we are passing in a single requirement ("can they 'view' this resource?"). The name would equal 8 in this case (yes, they're out of order) and we pass it into the handler and compare it with the User's Claims value to see if they are authorized to perform the operation.

This is where the AuthorizationService comes into play.

Wherever you need to authorize someone to access a resource, add an IAuthorizationService to your class's constructor to dependency inject the service and perform the authorization for each resource id you want verified by operation (I called mine AuthService).

For our menu system, we'll use the menu Ids as our resource id to pass into our AuthorizationHandler.

var items = MenuManager.GetAll().ToList();
foreach (var menuItem in items)
{
    var viewResult = await AuthService
        .AuthorizeAsync(User, menuItem.Id, UserPermission.Read);

   menuItem.Visible = viewResult.Succeeded;
}

This gives you complete control over what a user can and can't do with resources in the application.

Conclusion

When a new object is introduced into a system (like a Project or Author entity), usually throughout the system, functions are associated with those objects.

Functions like Create, Retrieve, Update, and Delete (CRUD) are the most standard.

Can they create a project? Update a project? Can the user upload a file? 

In this post, we converted a versatile way of taking system functions (CRUD) and applied a resource authorization by user. Developers could take this and build an administrative control panel to manage users and their resource operations. You can go even further and relate resources to roles.

Once these functions are defined and applied to a particular user, the administrator would control the authorization of resources in a centralized location. 

How do you implement resource-based authorizations? Is there a third-party application to achieve this? Post your comments below and let's discuss.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as a Principal Software Engineer.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus