Part 1: A better way to handle authorization in ASP.NET Core

Last Updated: November 14, 2021 | 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.

UPDATE: New library that makes applying the “better way” MUCH easier

This approach has been so popular that I have created an library called AuthPermissions.AspNetCore which implements the roles features explained in this article and the multi-tenant system described in the part 4 article Please look at the articles called Finally, a library that improves role authorization in ASP.NET Core and the detailed documentation (including links to videos) for the more information.

NOTE: The new AuthPermissions.AspNetCore library follows the approach at this article and the other articles in this series, but does add a number of improvements and features, such as supporting JWT Tokens, support for Azure AD and so on.

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

TL;DR; – summary

  • ASP.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 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 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.

UPDATE: In article “Part 3: A better way to handle ASP.NET Core authorization – six months on” I show a way to handle Roles via the Roles-to-Permissions database. That makes the approach more useful with other authentication approaches such a social media, AzureAd etc.

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,
//... other code left out

The things to note are:

  • I show two types of permissions.
    • First four (lines 4 to 11) 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 when working out what permissions the user should have – 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?

UPDATE: Two improvements added in later articles:

  1. In article called “Part 3: A better way to handle ASP.NET Core authorization – six months on” I show an easier way to turn Roles into permission claims using the UserClaimsPrincipalFactory- see this section.
  2. In the second version of the code in the PermissionAccessControl2 repo I improved/simplified the code called by ‘OnValidatePrincipal’ and provided versions for various extra features I added. See the folder “AuthorizeSetup” for the code.

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 context.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:

  • Create the user (if an invite-only type application)
  • 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:

  • Startup. 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 and makes you think of better ways of building better authentication systems for your projects.

UPDATE: See new code in PermissionAccessControl2 repo and new articles.

The new code and articles add more features, and more especially make the code easier to copy into your own application. See article called “Adding the “better ASP.NET Core authorization” code into your app” which gives you a step by step tutorial on how to add the features in this and later articles into your own code.

Further reading

Happy coding. PS. 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

4.6 10 votes
Article Rating
Subscribe
Notify of
guest
70 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
tan
tan
1 year ago

so great work you have done
do you have any version of this which upgrade to dotnet6?
I am try to upgrade it ,but find some hard work I can’t deal

and further more,did you have any plan to make all this prj upload to a nuget ?

tan
tan
1 year ago
Reply to  tan

I have find the AuthPermissions.AspNetCore
thanks for your great work

Nileksh
Nileksh
2 years ago

Instead of theory can you please provide video lecture for each step. It will very easy to us to understand things…

a-a-k
a-a-k
2 years ago

Great thanks for your work! But, what do you say about mixing identity and permissions? I know that some experts like Dominick Baier have something to say about this.

a-a-k
a-a-k
2 years ago
Reply to  Jon P Smith

I agree, btw we use not same as you, but very very close custom auth system based on modules/permissions. I also can’t understand what exactly is the problem with using permissions as a part of identity. We’re doing that and it works fine. Now, we’re trying to make a migration to IdentityServer4 instead of our custom system, so I’m looking up for any advantages/disadvantages of that. And, unfortunately, all that I could found is just a recommendation of PolicyServer. Also, I don’t know yet about IdentityServer’s design for permissions usage (and I’m afraid there is no any since it is not made for that purpose).

Bayo
Bayo
2 years ago

Thanks a lot Jon for this very detailed article. I have one question. Does AspNetRoleClaims in do the same as Permissions in your articel. Documentation for it is very sparse, but from what I could glean of some websites, RoleClaims hold the claims for Roles which might as well be permissions for the said roles.

Murali
Murali
2 years ago

I kept break point in HandleRequirementAsync Method. But my debug is not going to the break point? Could you please tell me the issue why debug is not going to the break point kept in handler?

Murali
Murali
2 years ago
Reply to  Jon P Smith

Jon Thank you so much for reply.

My Debug when to break point in PermissionHandler. I forgot to keep below two services in my startup.cs file.

//Register the Permission policy handlers
      services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
      services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

after keeping the above two services in my startup.cs file then my break point when to the permissionhandler file.

Your code helped me a lot. I took reference of your code and I modified and keep my own logic and code and got “permissions check”.

Thanks a lot for you(Jon)

Ford Zabasky
Ford Zabasky
3 years ago

Hi Jon,
Great series of articles… was wondering do you have a git repo of the PermissionAccessControl project in MVC5? I’ve inherited a MVC5 project that uses Kentico as a headless CMS (users in kentico cms_user table) but need to roll the Roles and Permissions in a separate database like your project. Thanks!

Sick mo
Sick mo
2 years ago
Reply to  Jon P Smith

Mvc5 has session and custom attributes, very simple, easy and fast .

Dave Nay
Dave Nay
3 years ago

Love it! We are in the process of migrating a WinForms desktop application where this type of fine-grained permissions is (relatively) easy to a ASP.NET application, and I hadn’t yet found a solution this well documented.

Jon P Smith
3 years ago
Reply to  Dave Nay

Thanks Dave for the comment and Tweet. Nice to know my many days of work putting this together helps.

Dai Nguyen
Dai Nguyen
3 years ago

What happen if I has a new permission? Your project must Re-Build, add new permission enum, add attribute header… I think it is not good architecture. 🙂

Jon P Smith
3 years ago
Reply to  Dai Nguyen

Hi Dai,

I may have not explained this approach well enough. The idea is that the permissions are linked to features in your code, so a new permission will only be created when you add, or alter, the code. Permissions are applied to ASP.NET Controller actions or Razor Page authorizations so they are directly linked to your code. So, new permissions are only added if you change your code.

The user’s Roles and the Permissions in a Role are held in a database, so they can change dynamically without needing to republish the code. That is a big difference from the standard Role usage in a ASP.NET application, where changing the Roles in your code needs you to rebuild your application.

I hope that helps.

Dai Nguyen
Dai Nguyen
4 years ago
Reply to  Jon P Smith

Nice, I think we can authorize dynamically. It is only structure responsibility based on purpose of each project we do. Thanks a lot!

Olivier Oswald
3 years ago

Interesting approach, although I wonder why you do it this way. Can’t you simply declare the permission “CanApplyForHoliday” being a standard Core Identity Role and use [Authorize(Permissions.CanApplyForHoliday)]? In the database you add your users, roles and permissions with corresponding relationships (not using the AspNetUsers and AspNetRoles tables of course). After authentication you combine the Permissions of the user and feed that into Core Identity Roles. But perhaps I did not fully understand…

Jon P Smith
3 years ago
Reply to  Olivier Oswald

There are two reasons I think Roles+Permissions are better that Roles on their own. The main reason is that Roles applied via a permission are fixed, so if you want to change an authorization you have to edit your code and redeploy it – using a database to map roles to permissions means that you can dynamically change the authorization.

The other main reason is more philosophical. I think users have Roles, e.g. “Manager”, “Staff”, etc. which are broad terms referring to their job role (maybe with some function Roles like “UserAdmin”). While when you are dealing with an Controller action or Razor Page, then you are dealing with a specific function, like “CanApplyForHoliday”, “CanSeeHolidayCalender”.

If Roles on their own work for your business need, then that’s fine. But for my client, where changing the authorization (including based on the modules the company has purchased) ruled out Roles on their own.

Olivier Oswald
4 years ago
Reply to  Jon P Smith

Please read again 😉 My comment was about using what you call “Permission” as a the “Core Identity Role”. What you call “Role” is the entity that glues users and permissions. This “Role” entity is not used in the Controller at all, only in the “Permissions” that derive from it. See what I mean?

André N. Darcie
3 years ago

Hi Jon!
Thanks for this great article, very well explained and executed.

I have some problems tho: how could I implement these steps using JWTs (Json Web Tokens)?

Jon P Smith
3 years ago

Hi André,

The approach will work with JWTs – you need to add the permissions to the JWT token. I did look at this for my client, but we went with Cookies because of some security issues with JWTs.

Microsoft don’t have a lot on JWTs in their documentation, but have a look at https://devblogs.microsoft.com/aspnet/jwt-validation-and-authorization-in-asp-net-core/

Alejandro
Alejandro
3 years ago

Hi Jon,
Excellent article, congrats and thanks for your amazing explanation!!!!

I’d like to use a SQL Database to test this, I could create the .NET Core Authentication default database, but I couldn’t create the others tables, for example RolesToPermissions.

Could you share how is the structure of these tables?

Thanks
Alejandro

Jon P Smith
3 years ago
Reply to  Alejandro

Hi Alegandro,

Thanks for your comments. There is a complete GitHub repo that goes with this you can find it at https://github.com/JonPSmith/PermissionAccessControl .

I used EF Core to create and seed the database on startup. I used an in-memory Sqlite databases, because it was easier for people to run locally. You shouldn’t have any problem creating a SQL database using EF Core.

Alejandro
Alejandro
4 years ago
Reply to  Jon P Smith

Perfect Jon, thanks for your quick reply.

I’m going to check it, and I’ll back to you if I need some advise.

Again, congrats
Alejandro

Gapwe
Gapwe
3 years ago

Hi Jon !
I’m currently struggling with the way I want to handle authorization in my app. This article is exactly what I was looking for except on one thing.
In your project using this technique, how would you handle permission based on a resource?
In mine, I’m on the way to use permissions to authorize the access to the different actions as a first protection but I also need to use resource based authorization (for example : check if the User is the Owner of the data he’s trying to CRUD).

Jon P Smith
3 years ago
Reply to  Gapwe

Hi Gapwe,

Have you seen part 2 of this article (use this link to get to it)? The second article covers “data authorization”. I don’t think it covers quite what you asked for, but it might give you some thoughts on how to implement it. The key is the UserID

Jon P Smith
3 years ago
Reply to  Gapwe

Hi Gapwe,

Something strange has happened to the comments on part 2 of this article – they have all gone. Is that the same for you?

I have an email with your questions so I can reply, but I’m hoping the comments come back on part 2.

Gapwe
Gapwe
4 years ago
Reply to  Jon P Smith

Yes, It is the same for me.
Weird.

I hope they’ll come back too. There were many good toughts that helped me in there

I’m currently implementing the RoleToPermission to my project :p

Harutyun Imirzyan
Harutyun Imirzyan
3 years ago

Hi Jon,
I see you have done a hard work and achieved your goal. But actually this isn’t the recommended way of doing this task. The idea of having permissions and distinguishing them from roles is great, but you should’t store all these staff in user claims. Claims actually represent the user identity and not the authorization data. Consider the case when a user has a lots of permissions. It will cause huge cookies/JWT token which is bad. Moreover, when using JWT token, consider the case of changing permissions of one user. The token will still contain not actual data which will potentially cause security vulnerabilities.
I have thought of this task a lot, and came to approach of PolisyServer (https://policyserver.io/) (https://github.com/PolicyServer/PolicyServer.Local). Currently I’m trying to implement the permissions/policies by using this approach.
Thanks

Jon P Smith
3 years ago

Hi Harutyun,

My first idea was to use the PolicyServer and I did try it out. In the end we rejected it because of three reasons:
1. We couldn’t get information to show it was going to work for our situation until we paid.
2. It was very expensive to buy.
3. I had some concerns on its performance.

On your point about lots of permissions could overflow a cookie size. You may have missed that I store the permissions as a string of the enum values. For 100 permissions I can create a string of 403 chars with a very simple hex encoding. For my client I did another level of compaction but in hindsight we didn’t need it.

On the changing permissions issue we did talk that through with the client and it wasn’t a problem. However if this was an issue then we could time-out the cookie, which would give some level of control.

Putting the permissions in the user claims made for a very fast implementation (no database access). Also the Roles are in there too, so I don’t think I’m changing any concepts. But I do strongly agree with you that the user’s identity is very different from the user’s authorisation.

Having said all that would be very interested in your implementation as there numerous ways to tackle this problem. If you write about it I would love to read it so send me a link if you do.

Harutyun Imirzyan
Harutyun Imirzyan
4 years ago
Reply to  Jon P Smith

Oh, I had missed the fact that you’re using enum bit flags. I had thought of it previously, but it has a limit of 64 permissions, so I decided not to use that approach. I’ve already examined the code of PolicyServer.Local, and now I’m trying to implement my own version of PolicyServer. When I finish the implementation, I’ll share more details about it. Most of my thinking is the performance. I hope I’ll finally implement a working solution for this problem.

Mark
Mark
3 years ago
Reply to  Jon P Smith

Hi Jon,
Thanks for the detailed write up; I really appreciate every article you publish and I have purchased your Second Edition book.

“On the changing permissions issue we did talk that through with the client and it wasn’t a problem. However if this was an issue then we could time-out the cookie, which would give some level of control.”

I have some concern about the change in permission: if we would time-out the cookie to somewhat control this what would be a reasonable time-out balancing risk vs unnecessary issuing of new cookie?

Regards,
Mark

Last edited 3 years ago by Mark
Mark
Mark
3 years ago
Reply to  Jon P Smith

Thanks Jon, I will make sure to finish reading all the articles first!

keatkeat
keatkeat
3 years ago

nice article,
but some question not understand.
1.why asp.net core identity team not use this as a best practice ?
what are the disadvantages of your plan ?
2. use claim to store permission already break the rule.
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims?view=aspnetcore-3.0
“A claim is a name value pair that represents what the subject is, not what the subject can do”.
what do you think?

Jon P Smith
3 years ago
Reply to  keatkeat

Hi keatkeat,

Actually I regret using the word “better” in the article title as its better for the complex application I was working on, but too complicated for simple applications. But I’m not going to change it otherwise it will cause problems for SEO and old links!

The ASP.NET Core Roles and policy-based systems provide a great set of tools and I have harnessed them to build a authorization system with two features that was useful for my client (and generally). The two main features are:
a) You change the rules on what can be accessed without having to re-delpoy the application
b) It also enforces what modules (e.g. paid-for features) the user can access.

To answer your questions:

1. “what are the disadvantages of your plan”. It’s more complex and I only think its worth that complexity if you need its features.

2. “A claim is a name value pair that represents what the subject is, not what the subject can do”. I’m not really sure what that means! The Roles are in the claims, so why are permissions are different from that? You could put it in separate cookie, but I wanted it to benefit from the high security ASP.NET Core applies to the Identity cookie.

For me putting the permissions in the claims is was important to get the best performance. The alternative would be doing a database access for every HTTP request for a logged in user, and that isn’t ideal. The cookie is always going to be decoded, so adding the permissions there will only add a small performance cost for decrypting the extra bytes.

keatkeat
keatkeat
4 years ago
Reply to  Jon P Smith

Thank you for your reply.
In fact, I totally agree with your solution.
I am looking for a solution that allows users to assign role/task dynamics and your solution looks very good.
just because seeing the merits of one thing, but not seeing the shortcomings makes me uneasy.
i will have a try.

Michael Clark
Michael Clark
3 years ago

I think I’ve found a hole.

From what I’ve understood from doing my reading on Brock Allen’s blog Isn’t it a bad idea to put permissions in claims? Claims are very hard to update/remove once they’ve been given. They usually require a user to logout and back in again to refresh them (they’re usually stored in a cookie for use across request). Also contextually I think they’re supposed to represent a users identity and not contain app specific properties like permissions.

Here’s the hole: If you were to update a users permissions to deny him permission to something, he’d continue to have access until he refreshes his claims (have to logout and log back in again before denied access). So you lose out on that real-time permission update.

Might not be an issue for every app – but it’s worth thinking about. Also, where you may be losing out on the real time permission change, you probably make a win on the performance of the app (cause you don’t have to look permissions up for each authorised endpoint which is usually a db call).

Nice article. Btw, tell me if I’m wrong – I’ve been wrong before and for some reason I enjoy it.

EDIT: I scroll down the comments and I realise someone else has just said what I have said. Have read your response and makes sense. Again, thanks for the blog post. This is worth a look btw, also looks like a nice solution: https://github.com/PolicyServer/PolicyServer.Local

Jon P Smith
3 years ago
Reply to  Michael Clark

Hi Michael,

You saw my responses to Harutyun, but I thought I would add a bit more here as its a good question.

You are right that the user permissions in this design are fixed until the user logged out, but there are ways around it but they add complexity and possibly performance issues. I talked this though with the client and they were happy with a “have to log out to get changed permissions”, but we did make sure we didn’t reuse permission values in case we did a software change and someone was still logged in (note the obsolete attributes used on the enums to stop reuse).

Let me explain the way around updating the permissions and you can decide which one you want to use:

1. The simplest thing you can do is add a timeout to the Cookie (see this link https://stackoverflow.com/a/34981457/1434764 ). I haven’t tried that, but by forcing the Cookie to be recreated you would get the permissions updated.

2. The ValidateAsync method is called on every HTTP request which gives you some options.For instance you could have DateTime of when the permissions were last updated and and a DateTime when the cookie was updates. That needs some form of global value to check, which brings in some complications, but its doable. This is one idea but I’m sure you can think of more.

On the point that its a “bad idea to put permissions in claims” I don’t really get that. We are happy to have Roles in the Claims, so why not permissions. Obviously you don’t want the permissions to be big (especially when using Cookies), which is why I encode all the permissions into a string.

On PolicyServer we did consider that but there were a few issues (which may have changed now) that made us build our own system. Note that all the code here is rewritten into this open-source project with my client’s blessing (the client’s implementation is MUCH more complex and this gives them some free documentation on the overall approach).

Christian Schmitt
Christian Schmitt
4 years ago
Reply to  Jon P Smith

You can actually implement an ITicketStore which pulls the ClaimsPrincipal from the database on each request, when using Cookie Based Authentication (or Identity).

I’ve just stumbeld about ITicketStore because our Cookie is quite big (it will get split automatically to 3 cookies). Since we use roles as permissions, however we have a second app where I wanted to try Users, “Groups” and Permissions on Users and Groups so a User has a combined Permission Set of Groups + Users and stumbled upon your great article.

Matthijs Hoekstra
Matthijs Hoekstra
4 years ago
Reply to  Jon P Smith

After seeing your session today I think there indeed other approaches to mitigate but with every architecture decision there are trade-offs. You don’t need to store the claims back in the cookie as long as there is a way to determine the permissions again with every request (and build the principal with the claims so the code stays the same), You could cache this but what happens if something changes and the cache on multiple servers are different? Perhaps a value in the cookie you check so you can decide to refresh the cache etc. But that’s another topic 🙂

Storing the claims in the token makes keeping state much easier, but it will also increase the cookie size.

Lara
Lara
3 years ago

Hi Jon,

I’m trying to get your code to run without using SQL lite and in memory storage, but I seem to have trouble with the async methods and the timing of executions to the database. I’m using SQL server and these are the only two lines of code I’ve added (and took out the SQL lite counterparts) :

services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString(“RolesConnection”)));

services.AddDbContext(
option => option.UseSqlServer(Configuration.GetConnectionString(“IdentityConnection”)));

I think SQL server takes longer to store the data, since the user data is almost always stored if I run the app, but the role data is sometimes stored , sometimes only partially stored and sometimes not stored at all. This leads to exceptions in the app. Same for the rolestopermission and ModuleForUsers table. These mostly do not have any data written to it when I run the app, but sometimes there is. By playing around with delaying of the async methods I’ve gotten the app to deploy and I could actually use it like with the SQL Lite, but even then it does not work everytime I run the app. The moment I change back to inmemory everything works fine again. Any help would be appreciated.

Jon P Smith
3 years ago
Reply to  Lara

Hi Lara,

Sorry about that. The only area I can think is some code for setting up the databases and the users. Let me deal with the first problem, which sounds like a async problem, and then go onto another problem about duplicate data in the database.

1. Async problem

There is a ‘nasty’ bit of code in the Startup’s Configure method where I run async inside a sync service to setup the databases. This is a known problem, and should be fixed in 3.0.

I looked up the fix on stackoverflow and changed the code to:

serviceProvider.AddUsersAndExtraAuthAsync().GetAwaiter().GetResult();

I hope that fixes it, but I can’t be sure. Please let me know.

2. General database issues when swapping to a real database

The other problem you will have is the seeding of the data and users. With in-memory databases I know they will be empty every time so I add all the data and users on EVERY startup. That really won’t work for a database that already exists, because it will keep adding duplicate data and I think it will fail when the code tries to add a user that are already exists.

All the code for seeding the databases (data and users) is in the class `StartupExtensions` in the StartupCode directory of the web app. You could just remove it all and replace with the code you want or change the code to only add data if there aren’t any entries in the database, and similarly check if a users exists before trying to add the same user again.

Not so important but the Program class has code to create the databases if they aren’t there (should be OK, but not the ‘proper’ way to create databases).

Lara
Lara
3 years ago
Reply to  Jon P Smith

Hi Jon,

Thanks for your reply!

1.) On the Async problem, unfortunately your fix doesn’t work, I get the following error message:
“A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations”. This is one of the error message I got before the change as well.

At the moment I can get the app to work perfectly by doing a blocking delay”
Task.Delay(300).Wait(); before this line in StartupExtensions.cs:
var result = await userManager.CreateAsync(user, user.Email); //email is the password

This seems to be the only change necessary for the app to work, but doesn’t seem like a good solution.

2) For testing purposed I’m just deleting the data from my tables before running the app again. I’ll build in checks later, but just to get the hang of this roles to permission approach this is the least of my worries.

Jon P Smith
3 years ago
Reply to  Lara

Hi Lara,

I tried another approach. I made the Program Main async and moved the setup to there. That should work.

You still need to decide what to do about the database setup.

Jon P Smith
3 years ago
Reply to  Lara

Hi Lara,

If you don’t need the users/data in the databases then you could just comment out the call to the AddUsersAndExtraAuthAsync method. That will stop the asycn problem.

Pieter
Pieter
3 years ago

Hi Jon,

Thanks for the great article. I’ve scaffolded the standard asp.net core identity to customize the details required and was looking for a way to implement something similar to what you’ve done here. As a trail I transferred all your class libraries and updated my main applications code to suit. I am however having a problem similar to Lara.

I’ve tried the Program.cs async main, but still the error pops up. Even with the delay that Lara used it persists, but as soon as I comment out the AddUsersAndExtraAuthAsync the program runs without any issues.

Do you have anything else I can give a go?

Thank you in advance.

Jon P Smith
3 years ago
Reply to  Pieter

Hi Pieter,

Could you show me the error you get as it hard to work out what’s happening without that. It might be better if raise an issue on the GitHub repo as these comments don’t format well. Please also let me know what you have done about the database, i.e. have you swapped out the in-memory for a real database (see my comments to Lara in the section General database issues when swapping to a real database).

Pieter
Pieter
4 years ago
Reply to  Jon P Smith

Hi Jon,

I went through the comments you made to Lara before the post, no joy…

I opened an issue with more details on:
https://github.com/JonPSmith/PermissionAccessControl/issues/2#issue-451085648

Jon P Smith
4 years ago
Reply to  Pieter

Hi Pieter (and anyone else),

I plan to work on this companion GitHub application so that it contains a version that will work with a real database (plus a few other small tweaks). That should happen in a few weeks time – watch out on Twitter @thereformedprog for an announcement of any changes.

Qusai Hamed
Qusai Hamed
3 years ago

Hi Jon,
it’s a nice and really useful article for medium and large applications. I tried to apply this on my current project which has users and roles. Also, I would like to apply permissions as well. after I complete there is no runtime error but after the user login. the application couldn’t return (get) HTML so a blank page shows and the error description shows in console part in browser.
Please let me know if there is a way to debug kind of those errors.

with Edge browser the error is: (HTTP500: SERVER ERROR – The server encountered an unexpected condition that prevented it from fulfilling the request.) but with firefox shows (The character encoding of the plain text document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the file needs to be declared in the transfer protocol or file needs to use a byte order mark as an encoding signature.)

Qusai Hamed
Qusai Hamed
3 years ago
Reply to  Qusai Hamed

it’s working fine after I do this, take “AddScoped” out from if statement in startup file as in image
comment image

Jon P Smith
4 years ago
Reply to  Qusai Hamed

Hi Qusai,

I’m travelling so I can’t get to my code at the moment, but i strongly recommend you move to using the code in new version of the application at https://github.com/JonPSmith/PermissionAccessControl2

A few people had problems with the first version, mainly because it only works with in-memory databases. The new version is designed to work with both in-memory databases and real databases, e.g. SQL Server. See the Readme file for info on how to configure it and see the two new articles, Part 3 and Part 4. The links for those are at the start of this article in blue.

Khoren Mansuryan
Khoren Mansuryan
3 years ago

https://www.thereformedprogrammer.net/a-better-way-to-handle-authorization-in-asp-net-core/#putting-it-all-together
In this part,there is two links that refrs to git repository, but the don’t work, maybe location of the files were changed

Jon P Smith
3 years ago

Hi Khoren,

I had a quick look but couldn’t find them. But what I would say is you should look at the https://www.thereformedprogrammer.net/a-better-way-to-handle-asp-net-core-authorization-six-months-on/ article, which links to the much improved code https://github.com/JonPSmith/PermissionAccessControl2 – that code has more features and is better laid out.

Dat Hoang
Dat Hoang
3 years ago

Hi Jon,
I am building a web application using WebApi backend and Angular frontend using similar authorization approach as you.
Just want to ask about your personal experiences with put user’s permission in the claims. This means the permission will be store in the token and transfer within every request.
The other way around is check the database for user permission for every request. I don’t cache permission because it may change while the user login.
So what do you think is better in term of performance and security ?

Jon P Smith
3 years ago
Reply to  Dat Hoang

Hi Dat,

I’m sorry I didn’t reply – I use Disqus for comments and its stopped
sending notifications – I only found this because someone asked another
question and I saw your comment.

Firstly, in my solution I used a cookie because it is more secure (ASP.NET Core encrypts its identity cookie). On of the big issues with my permissions is to make them small, as cookies have a 4000 byte limit.

I hope that helps, even if its a bit late!

Pelle
3 years ago

Hi Jon and thanks for a very good article.
How to extend this even more?
Say you have one module named Holiday with two enum canRequestHoliday and CanApproveHoliday.
We have user A, B, C and D plus Manager E and F.
All 4 Users have canRequestHoliday and the two Managers have canApproveHoliday.
However Manager E can only approve for hus team (User A and B) and manager F only for team containing C and D?

Or a module for invoicing where you have 4 companies that can create their invoices. But the user for this companies should only be allowed to create invoices for their own company but one user owns two of the companies so he should be allowed to create invoices for both his companies.

Any idea on how to set this up?

Regards Pelle

Jon P Smith
3 years ago
Reply to  Pelle

Hi Pelle,

I’m sorry I didn’t reply – I use Disqus for comments and its stopped sending notifications – I only found this because someone asked another question and I saw your comment.

Once you start saying “…can only approve for his team” and ” then it sounds like a multi-tenant system. At that point you need to combine a user’s permissions with a data key, which I cover in Part 2 (see https://www.thereformedprogrammer.net/part-2-handling-data-authorization-asp-net-core-and-entity-framework-core/).

And your statement “one user owns two of the companies…” says you have a hierarchical multi-tenant system, which I describe in Part 4 (see https://www.thereformedprogrammer.net/part-4-building-a-robust-and-secure-data-authorization-with-ef-core/)

I expect you have a solution by now, but at least you have some more info for the future.

Pelle
3 years ago
Reply to  Jon P Smith

Hi Jon, better late respons than noone. 🙂
I have read all your parts and part4 is close but your solution have same permission for all datakeys, but I need different permission for different datakey. My permissions is more like ”level 1-5” than ”true/false” and the datakey is not hierarchical but I think your solution can be used anyway.
I haven’t any solution in place yet, since I have focused on other parts for now.

Julio
Julio
3 years ago

Hi Jon, I really like your solution on handling authorization, but I’m wondering, have you come up with a way of automatically generating or omitting links to views depending on the permissions required on Razor? For example, instead of having a if statement checking if the user has the permission required to view a page and then rendering the link to said page, somehow attaching the permission to the view somehow and isolating this logic of checking and rendering the link to a single place?

I mainly ask because I feel like navigations/sidebars will end up cluttered with a lot of similar if statements to check which links should be rendered.

Regards.

Jon P Smith
3 years ago
Reply to  Julio

Hi Julio,

I don’t know what database access code you are using, but you might like to read Part 2 of this series (see https://www.thereformedprogrammer.net/part-2-handling-data-authorization-asp-net-core-and-entity-framework-core/) where I talk about managing what a user can access using EF Core Query filters. I should you could adapt that to your needs even if you aren’t using EF Core.