A better way to handle authorization in ASP.NET Core

Last Updated: January 14, 2019 | Created: December 14, 2018

I was asked by one of my clients to help build a fairly large web application, and their authentication (i.e. checking who is logging in) and authorization (i.e. what pages/feature the logged in user can access) is very complex. From my experience a knew that using ASP.NET’s Role-based approach wouldn’t cut it, and I found the new ASP.NET Core policy-based approach really clever but it needed me to write lots of (boring) policies.

In the end I created a solution for my client and this article describes the authorization part – I call it Roles-to-Permissions (the name will make more sense as you read the article). I have also build an example ASP.NET Core application, with all new code to support this article. This example application is quite different from my client’s system as I tap into ASP.NET Core built-in Identity system (the client’s system needed OAuth2). The example application contains about 60 lines that I copied (with my client’s permission) from the original implementation to create an open-source version (MIT licence) you can use.

This article is part of a series on authorization in ASP.NET Core

NOTE: you can Clone the GitHub repo and run locally – it uses in-memory databases so it will run anywhere. The application was written using ASP.NET Core 2.1: parts of the ASP.NET Identity is changing, but the overall concept will work with any version of ASP.NET Core.

This is a long article, so here are links to the major parts:

TL;DR; – summary

  • NET Role-based authorization system works for systems with simple authorization rules, but it has limitations, like the fact that you have to republish your code if you change the authorization rules.
  • Roles, with names like “Manager” or “ExternalBuyer” makes sense for users (human or external services) as they define a “Use Case” of what the user should be able to do.
  • But Roles don’t work well when applied to ASP.NET Core actions or Razor Pages. Here you need a much more fine-grained solution, with names like “CanRequestHoliday”, “CanApproveHoliday” – I call these Permissions.
  • The solution is to map the user’s Roles to a group of Permissions and store these in the User’s Claims.
  • Then I use ASP.NET Core’s new policy-based authorization system to check that the User’s Permissions Claims contains the Permission placed on the action/page they want to access.
  • There is an open-source example ASP.NET Core application to go with this article.

Setting the Scene – a look at different application security needs

If you understand ASP.NET’s authorization and authentication features then you can skip this section.

There are billions of web applications and the control of what you can do ranges for “anyone can do anything”, e.g. Google search, up to some military systems where access needs keys, biometrics etc. When you need to prove you are a valid user of the system, say by logging in, that is referred to as authentication. Once you are logged in then what you can do is controlled by what is called authorization.

Authorization breaks down into two parts:

  1. What data can I access? For instance, you can see your personal information, but not other people’s personal information.
  2. What features you can use? For instance, are you allowed to change the title of a book that you can see?

NOTE: This article only describes a way to manage the second part, what features can you use.

ASP.NET MVC and now ASP.NET Core have various systems to help with authorization and authentication. Some systems only need a simple authorization – I could imagine a very simple e-commerce system could get away with: a) No logged in – browsing, b) Logged in – buying, and c) Admin – Add/Remove items for sale. This could be done using ASP.NET Role-based authentication.

But many business-to-business (B-to-B) systems have more complex authorization needs. For instance, think of a human resources (HR) system where people can request holiday leave and their manager has to approve those requests – there is lot going on inside to ensure only the correct users can use these features.

Systems like my example HR B-to-B system often end up with lots of complex authorization rules. My experience is that the ASP.NET Role-based authentication starts to have problems implementing this type of system, which is why I created the Roles-to-Permissions code.

Another type of application that could benefit from the Roles-to-Permissions approach are subscription systems, where the features a user can access depend on what subscription they paid for. The Roles-to-Permissions approach can control the features that as user can access based on the subscription they bought.

Role authorization: what is it and what are its limitations?

Roles authorization has been around for years in the ASP.NET MVC application, and I have used it in a number of applications. Here is an example of a ASP.NET Core controller that can only be accessed by logged in users with either the “Staff” Role or the “Manger” role.

[Authorize(Roles = "Staff,Manager")]
public ActionResult Index()
{
    return View(MyData);
}

This works for applications that have fairly simple and well-defined Roles, like User/Admin or Staff/Manager/Admin, then Roles is a good choice. But here are some of the problems I have found:

  1. If you have lots of roles you can end up with long Authorize attributes, e.g. [Authorize(Roles = “Staff,HrManager,BizManager,DevManage,Admin,SuperAdmin”)].
  2. Because Authorize is an attribute then the string has to be a constant, e.g. you can’t have $”{RoleConstants.StaffRole}”. This means if things change you are editing strings, and you could misspell something really easily.
  3. The big one for me is your authorization rules are hard-coded into your code. So, if you want to change who can access a certain action you have to edit the appropriate Authorize attributes and redeploy your application.

My experience from previous applications using Roles-based authorization is me constantly having to go back and edit the authorize Roles part as I develop or refine the application. I have been looking for a better way for some time, and my client’s requirements spurred me on to find something better than Roles authorization.

The architecture of the Roles-to-Permissions system

1. Introducing Roles, Permissions and Modules

It turns out that there is nothing wrong with the idea of a user having Roles. A user (human or an external service) can typically can be described by their function or department, like “Developer” or “HR”, maybe with side Roles like “HolidayAdmin”. Think of Roles as “Use Cases for users.

NOTE: In the example application I have Roles of “Staff”, “Manager”, and “Admin.

But the problem is that Roles aren’t a good fit for the actions in the Controllers. Each Controller action has a little part to play in a Role, or to turn it around, a Role is made up of a series of Controller actions that the Role allows you to access.  I decided I would call the authorization feature on each action a “Permission”, and I used an Enum to define them. A permission Enum member might be called “CanReadHoliday”, “CanRequestHoliday”, “CanApproveHoliday”, etc.

NOTE: In the example application I have Permissions on my ColorController of “ColorRead”, “ColorCreate”, “ColorUpdate”, and “ColorDelete”.

Now that we have permissions we can provide another feature that of controls access to optional features, e.g. features that a user only has based on their subscription to the service. There are many ways of handling features but by combining optional features into the permissions makes it simpler to setup and control.

NOTE:  In the example application I have Permissions called “Feature1” and “Feature2” which are mapped to Modules with the same name.

2. How this is implemented

Having defined my terms, I’m going to give you an overview of the process. It consists of two parts: the login stage and the normal accesses to the web site. The login stage is the most complex with lots of magic goes on in the background. Its basic job is to convert the user’s Roles into Permissions and add it to the User’s information.

I have set up my example application to store the user’s claims in a cookie which is read in with every HTTP request and turned into a ClaimsPrincipal, which can be accessed in ASP.NET Core by the HttpContext property called “User”.

Here is a diagram of that login stage. It might not make a lot of sense yet, but I describe each part in the rest of the article. This diagram is to give you an overview.

The second part is simpler and covers what happens every time the logged-in user accesses a protected Controller action. Basically, I have a policy-based authorization with dynamic rules that checks the current User has the permission needed to execute the ASP.NET action/razor page.

NOTE: Don’t forget there is example application if you want to look at the actual code.

Now I’m going to build up the Roles-to-Permissions in stages and explain what each part does.

Why I used Enums for the Permissions

One of the down-sides of using Roles is it used strings, and I’m a little bit dyslexic. That means I can type/spell things incorrectly and not notice. Therefore, I wanted something where intellisence would prompt me and if I still typed it incorrectly it would be a compile error. But it turns out there are a couple of other reasons that make using an Enum for the permissions a good idea. Let me explain.

In a big application there could be hundreds of Permissions. This lead to two problems:

  1. If I use Cookie Authorization there is a maximum size of 4096 bytes for the Cookie. If I had hundreds of long strings I might start to fill up the Cookie, and I want some room for other things like my data authorization. If I can store the Enums permissions as a series of integers it’s going to be much smaller than a series of strings.
  2. Secondly, I want to help the Admin person who needs to build the mapping from Roles to permissions. If they need to scroll through hundreds of permission names it could be hard to work out which ones are needed. It turns out Enum members can have attributes, so I can add extra information to help the Admin person.

So, here is part of my Permissions Enum code

public enum Permissions
{
    //Here is an example of very detailed control over something
    [Display(GroupName = "Color", Name = "Read", Description = "Can read colors")]
    ColorRead = 0x10,
    [Display(GroupName = "Color", Name = "Create", Description = "Can create a color entry")]
    ColorCreate = 0x11,
    [Display(GroupName = "Color", Name = "Update", Description = "Can update a color entry")]
    ColorUpdate = 0x12,
    [Display(GroupName = "Color", Name = "Delete", Description = "Can delete a color entry")]
    ColorDelete = 0x13,

    [Display(GroupName = "UserAdmin", Name = "Read users", Description = "Can list User")]
    UserRead = 0x20,
    //This is an example of grouping multiple actions under one permission
    [Display(GroupName = "UserAdmin", Name = "Alter user", Description = "Can do anything to the User")]
    UserChange = 0x21,

    [Obsolete]
    [Display(GroupName = "Old", Name = "Not used", Description = "example of old permission"
    OldPermissionNotUsed = 0x40,

The things to note are:

  • I show two types of permissions.
    • First four (lines 4 to 1) are fine-grained permissions, almost one per action.
    • Next two (lines 13 to 17) are more generic, e.g. I have a specific “UserRead”, but then one permission called “UserChange” which allows create, update, delete, lock, change password etc.
  • Line 5, 7, etc. Notice that I give each enum a specific number. If you are operating a 24/7 application with a new version seamlessly replacing the old version, then the Permission numbed mustn’t change otherwise user’s Claims be wrong. That is why I give each enum a specific number.
  • Line 19. I also support the Obsolete attribute, which stops the Permission appearing in the listing. There are plenty of scary stories about reusing a number with unintended consequences. (Also, it you try to use something marked as Obsolete you get a warning).
  • Line 4 etc. I add a Display Attribute to each Permission Enum. This has useful information that I can show lots of useful information to help the person who is building a Role.
  • Line 4, 6, 8, 10. I “Group” permissions that are used in the same place. This makes it easier for the Admin person to find the things they want. I also number in Hex, which gives me 16 possible permissions in a Group (I tried 10 and you could go over that, so 16 is better).

Here is a list of some of the Permissions in my example application listed via the Users->List Permissions nav dropdown.

And the code that produced that output (link to PermissionDisplay class for the whole thing)

public static List<PermissionDisplay> GetPermissionsToDisplay(Type enumType) 
{
    var result = new List<PermissionDisplay>();
    foreach (var permissionName in Enum.GetNames(enumType))
    {
        var member = enumType.GetMember(permissionName);
        //This allows you to obsolete a permission and it won't be shown as a
        //possible option, but is still there so you won't reuse the number
        var obsoleteAttribute = member[0].GetCustomAttribute<ObsoleteAttribute>();
        if (obsoleteAttribute != null)
            continue;
        //If there is no DisplayAttribute then the Enum is not used
        var displayAttribute = member[0].GetCustomAttribute<DisplayAttribute>();
        if (displayAttribute == null)
            continue;

        //Gets the optional PaidForModule that a permission can be linked to
        var moduleAttribute = member[0].GetCustomAttribute<PermissionLinkedToModuleAttribute>();

        var permission = (Permissions)Enum.Parse(enumType, permissionName, false);

        result.Add(new PermissionDisplay(displayAttribute.GroupName, displayAttribute.Name, 
                displayAttribute.Description, permission, moduleAttribute?.PaidForModule.ToString()));
    }

    return result;
}

How to handle optional/paid-for features?

My client provides a Business-to-Business application and plans to add new features that customers can subscribe to. One way to handle this would be create different Roles, like “Manager”, “ManagerWithFeature1”, “ManagerWithFeature2” or add separate Feature Roles that you have to manually apply to a user. That works but is pretty horrible to manage, and human error could cause problems. My preferred system is mark Permissions linked to a paid-for feature filter them based on the User’s subscriptions.

Marking Permissions as linked to a module is easy to do with the Enums – I just add another attribute. Here an example of Permissions linked to a Module (see line 5).

public enum Permissions
{
    //… other Permissions removed for clarity

    [LinkedToModule(PaidForModules.Feature1)]
    [Display(GroupName = "Features", Name = "Feature1", Description = "Can access feature1")]
    Feature1Access = 0x30,
    [LinkedToModule(PaidForModules.Feature2)]
    [Display(GroupName = "Features", Name = "Feature2", Description = "Can access feature2")]
    Feature2Access = 0x31
}

The paid-for modules are again represented by an Enum, but one marked the [Flags] attribute because a user can have multiple modules that they have subscribed to. Here is my PaidForModules Enum code

[Flags]
public enum PaidForModules : long
{
    None = 0,
    Feature1 = 1,
    Feature2 = 2,
    Feature3 = 4
} 

NOTE I add “: long” to the Enum which gives me up to 64 different modules in my system.

What happens is that Permissions linked to a Module that the user hasn’t subscribe to are filtered out during the login stage (I show how later). This makes the setting up the Roles much simpler, as you build each Role with all the Permissions that make sense for that role, including features mapped to a paid-for module. Then, at login time, the system will remove any Permissions the current user doesn’t have access to. That is simpler for the Admin person and more secure for the application.

How do I turn the Roles into a Permissions Claim?

I my client’s system we uses 0Auth2 authentication, but for this example I used ASP.NET Core IdentityRole to hold the Roles that a user has. That means I can use all of the ASP.NET Core built-in Identity code to set up the Users and Roles. But how do I convert the User’s Roles to a Permissions Claim?

Again there are few ways to do it, but in the end I tapped into an event in the Authorization Cookie called ‘OnValidatePrincipal’ (here is a link to the lines in the example application startup class). This calls the code below, but be warned it’s pretty complex so here is a summary of the steps it goes through:

  1. If the Claims already have the Permissions claim type then nothing to do so return quickly.
  2. Then we get the Roles the user has from the Role Claim
  3. I need to access my part of the database. I can’t use dependency injection, so I use the extraAuthDbContextOptions, which is a singleton that I can provide at startup.
  4. Then I get all the permissions for all of the roles, with a Distinct to remove unnecessary duplicates.
  5. Then I filter out any permissions that are linked to a Module that the user doesn’t have access to.
  6. Then I add a permissions Claim containing all the Permissions the user is allowed, but packed as hex numbers in a single string so that it doesn’t take up so much room (I used Hex format as it made debugging easier).
  7. Finally I have to create a new ClaimsPrincipal and tell ASP.NET Core to replace the current ClaimsPrincipal, plus set the all-important ShouldRenew to true, which updates the Cookie, otherwise this complex (slow) method on every HTTP request!
public async Task ValidateAsync(CookieValidatePrincipalContext context)
{
    if (context.Principal.Claims.Any(x => 
        x.Type == PermissionConstants.PackedPermissionClaimType))
        return;

    //No permissions in the claims so we need to add it
    //This is only happens once after the user has logged in
    var claims = new List<Claim>();
    foreach (var claim in context.Principal.Claims)
    {
        claims.Add(claim);
    }

    var usersRoles = context.Principal.Claims
        .Where(x => x.Type == ClaimTypes.Role)
        .Select(x => x.Value)
        .ToList();
    //I can't inject the DbContext here because that is dynamic, 
    //but I can pass in the database options because that is a 
    //From that I can create a valid dbContext to access the database
    using (var dbContext = new ExtraAuthorizeDbContext(_extraAuthDbContextOptions))
    {
        //This gets all the permissions, with a distinct to remove duplicates
        var permissionsForUser = await dbContext.RolesToPermissions
            .Where(x => usersRoles.Contains(x.RoleName))
            .SelectMany(x => x.PermissionsInRole)
            .Distinct()
            .ToListAsync();

        //we get the modules this user is allows to see
        var userModules =
            dbContext.ModulesForUsers
                .Find(context.Principal.Claims
                     .SingleOrDefault(x => x.Type == ClaimTypes.Name).Value)
                ?.AllowedPaidForModules ?? PaidForModules.None;
        //Now we remove permissions that are linked to modules that the user has no access to
        var filteredPermissions =
            from permission in permissionsForUser
            let moduleAttr = typeof(Permissions).GetMember(permission.ToString())[0]
                .GetCustomAttribute<LinkedToModuleAttribute>()
            where moduleAttr == null || userModules.HasFlag(moduleAttr.PaidForModule)
            select permission;

          //Now add it to the claim
          claims.Add(new Claim(PermissionConstants.PackedPermissionClaimType,
              filteredPermissions.PackPermissionsIntoString()));    }

    var identity = new ClaimsIdentity(claims, "Cookie");
    var newPrincipal = new ClaimsPrincipal(identity);

    context.ReplacePrincipal(newPrincipal);
    context.ShouldRenew = true;
}

How do I convert my Permissions into Policy-based authorization?

OK, I now have access to the Permissions via the User’ Claims, but how do I get this turned into something that ASP.NET Core can use for authorization. This is where a .NET developer and friend, Jerrie Pelser helped me.

When I started this project, I emailed Jerrie Pelser, who runs the ASP.NET Weekly newsletter (great newsletter! Do sign up) as I know Jerrie is an expert in authentication & authorization.  He pointed me at a few architectural things and I also found his own article “Creating Authorization Policies dynamically with ASP.NET Core” really helpful.  Jerris’s article showed me how to build policies dynamically, which is exactly what I need.

I’m not going to repeat Jerrie article here (use the link above), but I will show you my PermissionHandler that is used inside the policy to check that the current User’s Permissions claim exists and contains the Permission on the action/Razor Page. It uses an extension method called ThisPermissionIsAllowed which does the check.

public class PermissionHandler : 
    AuthorizationHandler<PermissionRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, 
        PermissionRequirement requirement)
    {
        var permissionsClaim = context.User.Claims
            .SingleOrDefault(c => 
                 c.Type == PermissionConstants
                     .PackedPermissionClaimType);
        // If user does not have the scope claim, get out of here
        if (permissionsClaim == null)
            return Task.CompletedTask;

        if (permissionsClaim.Value
            .ThisPermissionIsAllowed(
                 requirement.PermissionName))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

There are two other classes that are involved in making dynamic policy-based authorisation work. Here are the links to them:

Policies are defined by strings, but as I said I hate strings as I can make a mistake. I therefore created this very simple HasPermission attribute which allows me to apply an Authorize attribute, but using a Permissions Enum

[AttributeUsage(AttributeTargets.Method 
    | AttributeTargets.Class, Inherited = false)]
public class HasPermissionAttribute : AuthorizeAttribute
{
    public HasPermissionAttribute(Permissions permission) 
       : base(permission.ToString()) { }
}

That’s pretty simple, but it means I get intellisence when I am adding the Permission.

Putting it all together

So, we have the Permissions in the code and we can apply them using our HasPermissionAttribute to each action we want to protect via authorization. Here is one action taken from the ColorController in my example application.

[HasPermission(Permissions.ColorRead)]
public ActionResult Index()
{
    return View(MyData);
}

We also need to add two tables to whatever database your application uses. The two EF Core entity classes are:

Once the application is up and running an Admin-type user has to:

  1. Create some roles, e.g. “Staff”, “Manager”, etc. using ASP.NET Core Identity code.
  2. Create matching RoleToPermissions for each of the roles, specifying what Permissions map to each Role.

Then, for every new user an Admin person (or some automatic subscription code) has to:

  1. Create the user (if an invite-only type application)
  2. Add the correct Roles and ModulesForUser for that new user.

Once that is done all the code I have shown you takes over. The user logs in, gets the Permissions and what they can access is managed by ASP.NET Core’s policy-based authentication.

Things I didn’t cover elsewhere

There are a few things I didn’t cover in detail, but here are links to the items:

  • The important parts about registering things are shown in highlighted lines in this link to the Startup class. (NOTE: I built the application with ASP.NET Core 2.1, but I know the Identity parts are changing in 2.2, so you might have to update the code I put in the Startup class for newer versions of ASP.NET Core).
  • You don’t need to use ASP.NET Core Identity system at all – I said the client’s version uses an external authentication system. You just have to create a Roles-to-User table so you can assign Roles to each user.
  • I didn’t cover how I Packed/Unpacked the Permissions. You can find the Extension methods for doing that in PermissionPackers.
  • You might want to check for a permission in your razor pages to show/hide links. I created a simple method in the PermissionsExtension class, and used it in the _layout.cshtml Razor page.

Conclusion

Well that is a long article so well done by getting to the end. I have described an authentication I have built that handles complex authentication rules while being (relatively) easy to understand and manage via the Admin staff. Sure, if you have hundreds of Permissions it’s not to be hard setting up the initial RolesToPermissions, but the Admin has a lot of information to help them.

For me the Roles-to-Permissions approach solves a lot of problems I had in older systems I built using ASP.NET MVC Roles. I have to write some more code, but it makes it a) easier to change the authorization rules and b) helps the Admin person manage applications with lots of Roles/Permissions. I hope it helps you think of better ways of building better authentication systems for your projects.

Further reading

Happy coding.

Don’t forget to sign up to Jerrie Pelser’s, ASP.NET Weekly newsletter if you are interested in ASP.NET or Entity Framework. It is the most important newsletter I get.