Finally, a library that improves role authorization in ASP.NET Core

Last Updated: December 29, 2021 | Created: August 10, 2021

In December 2018 I wrote the first article in the series called “A better way to handle authorization in ASP.NET Core” which describe an approach to improving how authorization (i.e., what pages/feature the logged in user can access) in ASP.NET Core. These articles were very popular, and many people have used this authorization/data key approaches in their applications.

Back in 2018 I didn’t think I could produce a library for people to use, but I have finally found a way to build an library, and it’s called AuthPermissions.AspNetCore (shorten to AuthP library in these articles). This open-source library implements most of the features described in the “A better way to handle authorization” series, but the library will work with any ASP.NET Core authentication provider and now supports JWT Tokens.

NOTE: At this time this article was written the AuthPermissions.AspNetCore library is/was in preview (1.0.0-preview) and I am looking for feedback before taking the library to full release. Please look at the roadmap discussion page for what is coming. Also, the preview some features won’t work on a web app running multiple instances (called scale-out on Azure).

This first article is focused on the improvements the AuthP library provides to using “Roles” to manage what feature users can access, but the library contains a number of other useful elements. The full list of articles is shown below:

TL;DR; – summary

  • The AuthPermissions.AspNetCore library has three main features:
    • Implements an improved Role authorization system (explained in this article).
    • Implements a JWT refresh token for better JWT Token security (see video and docs)
    • Includes an optional a multi-tenant database system (see video and docs)
  • The AuthPermissions.AspNetCore library can work with
    • Any ASP.NET Core authentication provider.
    • Either Cookie authentication or JWT Token authentication
  • This article focuses on AuthP’s improved “Roles / Permissions” approach, which allows you to change a Role without having to edit your application. Each user has a list or Roles, each Role containing one or more enum Permissions.
  • When the user logs in the AuthP library finds all the Permissions in all the Roles that the user has. These Permissions are packed into a string and added as a “Permissions” claim into the Cookie authentication or the JWT Token.
  • The AuthP’s HasPermission attribute / method will only allow access if the current user has the required Permission.
  • See other articles or the AuthP’s documentation for more information.
  • You can see the status of the AuthP library via the Release Notes document in the repo.

Setting the scene – How the AuthP library improves Roles authorization in ASP.NET Core

If you not familiar with ASP.NET’s authentication and authorization features, then I suggest you read the ”Setting the Scene” section in the first article in the A better way to handle authorization” series.

Back in the ‘old days’ with ASP.NET MVC the main way to authorize what a user can access was by using the Authorize attribute with what are called “Roles” – see the code below, which only lets a user with either the “Staff” or “Manager” Role to access that method/page.

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

ASP.NET Core kept the Roles approach but added the more powerful policy-based approach. But I found both approaches to have limitations:

  • Limitations of the ASP.NET Core Roles approach
    • The main limitation of the ASP.NET Core Roles is that authorization rules are hard coded into your code. So, if you want to change who can access a certain pages/Web APIs, you have to edit the appropriate Authorize attributes and redeploy your applicatios.
    • In any reasonably sized application, the Authorize attributes can get long and complicated, e.g.  [Authorize(Roles = “Staff, SalesManager , DevManage, Admin, SuperAdmin”)]. These are hard to find and maintain.
  • Limitations of the ASP.NET Core Roles policy-based approach
    • It very versatile, but I have to write code for each policy, which are all slightly different. And in a real application you might end up writing a lot of policy code.

From my point of view “Roles” are a useful concept for users, typically a user (human or machine) has a Role, or a few Roles, like SalesManager with maybe with an additional FirstAider role. Think of Roles as “Use Cases for users”.

But “Roles” are not a useful concept when defining what ASP.NET Core page/feature the user can access. That’s because for some feature you need fine-grained control, for instance a different access level for display, create, and update of sales information. For this a use what I call “Permissions”, for example you I might have SalesRead, SalesSell, SaleReturn, Permissions where the “Sales person” Role only have the SalesRead, SalesSell Permissions, but the” Sales manage” Role also has the SaleReturn Permission.

The AuthP’s approach says that “users have Roles, and your code uses Permissions to secure each page/WebAPI”. The AuthP’s Roles a number of benefits:

  1. No more deploying a new version of your application when you want to change what a Role can do – the AuthP’s Role/Permissions mapping is now in a database which admin people can change.
  2. The Permissions are declarative, just like the [Authorize] attribute (e.g. [HasPermission(MyPermissions.SalesSell)]) which makes it easier to maintain.
  3. You can have broad Permissions, e.g. NoticeBroadAccess (covering create, read, update, and delete), or fine-grained Permissions like SalesRead, SalesSell, SalesSellWithDiscount, SaleReturn for individual actions/pages.

NOTE: The AuthP’s Roles are different from the ASP.NET Core Roles – that’s why I always refer to “AuthP’s Roles” so that its clear which type of Role I am talking about.

How to use the AuthP’ library with its Roles and Permissions

So, let’s look at the various code you need in your ASP.NET Core application to use the AuthP’s Roles/Permissions system. Starting at the Permissions and working up the parts are:

  1. Defining your Permissions using an Enum
  2. Using these Permissions in your Blazor, Razor, MVC or Web API application
  3. Configuring the AuthP library in ASP.NET Core’s ConfigureServices method.
  4. Managing the user’s AuthP Roles and what Permissions are in each Role.

1. Defining your Permissions

The Permissions could be strings (just like ASP.NET Roles are), but in the end I found a C# Enum was best for the following reasons:

  • Using an Enum means IntelliSense can prompt you as to what Enum names you can use. This stops the possibility of typing an incorrect Permission name.
  • Its easier to find where a specific Permission is used using Visual Studio’s “Find all references”.
  • You can provide extra information to an Enum entry using attributes. The extra information helps the admin person when looking for a Permission to add to a AuthP’s Role – see the section on defining your Permission Enum.
  • I can use the Enum value: In the end I defined an enum with a ushort value (giving 65534 values), which can be stored efficiently in a (Unicode) string. This is important because the Permissions needed to be held in a claim and if you ASP.NET Core’s Cookie Authorization then a cookie has a maximum size of 4096 bytes.

So, let’s look at an example from Example4 in the AuthP’s repo, which is an application that manages a shop stock and sales. I have only shown a small part of Permissions in this example, but it gives you an idea how you can decorate each Permission to provide more info and filtering to the admin user.

public enum Example4Permissions : ushort //Must be ushort to work with AuthP
{
    NotSet = 0, //error condition

    //Here is an example of very detailed control over the selling things
    [Display(GroupName = "Sales", Name = "Read", Description = "Can read any sales")]
    SalesRead = 20,
    [Display(GroupName = "Sales", Name = "Sell", Description = "Can sell items from stock")]
    SalesSell = 21,
    [Display(GroupName = "Sales", Name = "Return", Description = "Can return an item to stock")]
    SalesReturn = 22,

    [Display(GroupName = "Employees", Name = "Read", Description = "Can read company employees")]
    EmployeeRead = 30,

    //other Permissions left out… 

    //Useful for setting up a SuperAdmin user
    [Display(GroupName = "SuperAdmin", Name = "AccessAll", 
        Description = "This allows the user to access every feature", AutoGenerateFilter = true)]
    AccessAll = ushort.MaxValue,
}

2. Using these Permissions in your Blazor, Razor, MVC or Web API application

AuthP can be used with any type of ASP.NET Core application, with three ways to check if the current user has a given permission.

2a. Using AuthP’s [HasPermission] attribute

For a ASP.NET Core MVC or Web API controller you can add the [HasPermission] attribute to an access method in a controller. Here is a example taken from Example2’s WeatherForecastController, which is Web API controller – see the first line.

[HasPermission(PermissionEnum.ReadWeather)]
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
    //… other code left out
}

2b. Using AuthP’s HasPermission extension method

If you are using Blazor, or in any Razor file you can use the HasPermission extension method to check if the current ASP.NET Core’s User has a specific Permission. Here is an example taken from AuthP’s Example1 Razor Pages application

public class SalesReadModel : PageModel
{
    public IActionResult OnGet()
    {
        if (!User.HasPermission(Example1Permissions.SalesRead))
            return Challenge();

        return Page();
    }
}

The HasPermission extension method is also useful in any Razor page (e.g. User.HasPermission(Example.SalesRead)) to decide whether a link/button should be displayed. In Blazor the call would be @context.User.HasPermission(Example.SalesRead).

2c. Using the IUsersPermissionsService service

If you are using a front-end library such as React, Angular, Vue and so on, then your front-end needs to know what Permissions the current user has so that the front-end library can create method similar to option 2, the HasPermission extension method.

The IUsersPermissionsService service has a method called PermissionsFromUser which returns a list of the Permission names for the current user. You can see the IUsersPermissionsService service in action in Example2’s AuthenticateController.

3. Configuring the AuthP library in ASP.NET Core’s ConfigureServices method.

The AuthP library has a lot of methods and options to set it up. It will work with any ASP.NET Core authentication provider that returns the UserId as a string.  

The code below uses ASP.NET Core’s Individual Accounts authentication provider in an MVC-type application – the highlighted lines contain the configuration code to set up the of the Permissions enum.

public void ConfigureServices(IServiceCollection services)
{
    //… other normal ASP.NET configuration code left out
    
    //This registers the AuthP library with your Permission Enum 
    services.RegisterAuthPermissions<MyPermissions>()
        //This sets up the AuthP’s database 
        .UsingEfCoreSqlServer(Configuration.GetConnectionString("DefaultConnection"))
        //This syncs AuthP to your ASP.NET Core authentication provider, 
        //In this example it’s the Individual Accounts authentication provider
        .RegisterAuthenticationProviderReader<SyncIndividualAccountUsers>()
        //This will ensure the AuthP’s database is created/migrated on startup
        .SetupAuthDatabaseOnStartup();
}

NOTE: There are many configuration parts to the AuthP’s library, and this shows a simple configuration appropriate for an existing application that is using ASP.Net Core’s Individual Accounts authentication. Please look at the AuthP startup documentation for a more detailed list of all the configuration options.

How the AuthP library works inside

The final part of this article gives you an overview of how the AuthP library works, mainly with diagrams. This should help you understand how AuthP library works.

When someone logs in some ASP.NET Core claims are built and stored in an the Authentication Cookie or in the JWT Token. The diagram shows the AuthP code in orange, with the blue part provided by ASP.NET Core and its authentication provider.

As you can see AuthP finds the user’s AuthP Roles and combine the Permissions in all the user’s AuthP Roles into a string (known as packed permissions), which becomes the Permissions claim’s value. This Permissions claim is stored in the Authentication Cookie or in the JWT Token.

This approach is a) very efficient as the Permission data is available as a claim (i.e., no database accesses needed), and b) a user’s AuthP Roles can be changed without needing to re-deploy your application.

NOTE: Normally the Permissions are only calculated when someone logs in, and any change to the Users’ AuthP Roles would only change by logging out and back in again. However, there are features in AuthP that can periodically re-calculate the user’s Roles/Permissions. I talk about this in this section of the document explaining how JWT Token refresh works.

The second part is what happens when a logged-in user wants to access a Page, Web Api etc. Because AuthP library added a Permissions claim to the Authentication Cookie / JWT Token the ASP.NET Core ClaimsPrincipal User claims will contain the Permissions claim.

When an [HasPermission(MyPermissions.SalesSell)] attribute is applies to a ASP.NET Core controller or Razor Page is calls a policy-based service that the AuthP library has registered. This Permission policy allows access to the method / page if the “MyPermissions.SalesSell” Permission is found in the User’s Permissions claim’s string. Otherwise, it redirects the user to either the login page or an Access Denied page, if already logged in (or for Web API it returns HTTP 401, unauthorized, or HTTP 403, forbidden).

Conclusion

This first article gives you an overview of the AuthP’s most important, that is the ability to change what a Role can do via an admin page instead of the ASP.NET Core Roles where a change requires you to edit your code and redeploy your application. In addition, the AuthP’s Permissions allow you to have very fine-gained access rules if you need it. Future articles will explain other features in the AuthP libraray.

The AuthP library is far from finished but I think it has the basics in it. The big limitation of the preview is that some features, like bulk loading on startup, only run on a single instance of the web app (i.e. no scale out). My plan is to solve that in the non-preview version of the AuthP library

I have put out a preview version to get some feedback. I have set up a roadmap discussion for you to see what I am planning and getting your suggestions and help. Please have a look at the library and add your comments suggestions to the roadmap discussion.

Happy coding!

4.9 13 votes
Article Rating
Subscribe
Notify of
guest
47 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
0blit
0blit
6 months ago

I’ve noticed during my testing that your HasPermissionAttribute has an important difference from Identity’s AuthrorizeAttribute… it doesn’t support inheritance.

I didn’t see reference to this in the documentation here (perhaps I missed it) – but I think this is an important distinction to mention.

It seems to be a conscious design decision (as you’ve set Inherted to false on the attribute). I’m thinking of using my own version of your attribute with Inherited set to true – I’ve tested it and it seems to work – but I’m just wondering if I’m missing something obvious?

Also, I see you’ve also blocked multiple attributes – so presumably its not possible to have multiple permissions granting access like it would be Identity? (i.e. any of one of a number of permissions granting access). I guess this makes sense given the granular nature of permissions in your library – just wanted to check my assumption is correct.

Example code illustrating behaviour difference:

[HasPermission(SomeAdminPermission)]
public class AdminBaseController : Controller
{
}

public class SomeAdminController : AdminBaseController
{
// In conventional Identity Authorise this would be restricted by the base controller permission
public IActionResult Index()
{
return View();
}
}

Anyhow, many thanks for such a useful library – it looks to do much of the heavy lifting.

Last edited 6 months ago by 0blit
0blit
0blit
6 months ago
Reply to  Jon P Smith

Hi Jon,

Thanks for the detailed reply and the linked hierarchy discussion.

The latter part of my question was indeed about defining multiple attributes. I understand your rationale and your hierarchy discussion is helpful. I find myself defining the same granular CRUD permissions you mention and I need to think more about this as a role.

The initial point I was trying to convey about inheritance was different. I was talking about the ‘scope’ of the Authorize vs HasPermissions attributes.

Specifically, HasPermission is defined as follows:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public class HasPermissionAttribute : AuthorizeAttribute

This is in contrast to the underlying AuthorizeAttribute which is defined as:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AuthorizeAttribute : Attribute, IAuthorizeData

This Inherited difference is a nuance I wasn’t expecting and just wanted to draw your (and others) attention to it as it could have inadvertently left some of my controllers unsecured.

We’re possibly a little unusual but we use a number of base controllers in our application (which have basic authorize constraints applied to them). We then have other controllers inherit from these base controllers. In Identity the derived controllers would inherit the authorisation constraints of their base classes. In AuthP this inheritance is lost and the derived controller is left open unless implicitly tagged with its own HasPermission attribute.

I’ve fixed this in our codebase trivially by defining my own version of your HasPermissionAttribute (with Inherited set to true). I just thought it was worth people being aware of this difference.

Thanks once again for such useful library. I hope to be able to incorporate it into my projects.

Nick
1 year ago

Hi Jon thank you for the library, I always enjoy reading your articles. I have been trying out AuthP with a .net core 6 web site. I managed to get things working but am having a problem with

services.ConfigureApplicationCookie(options =>
      {
        //this will cause all the logged-in users to have their claims periodically updated
          options.Events.OnValidatePrincipal =  PeriodicCookieEvent.PeriodicRefreshUsersClaims;
    }

When I use this configuration, it prevents a user from registering and I can see an error in the Chrome debugger “HTTPS to have cookies sent to same-site subresources”.
I have tried a few variations of setting the cookie options (SameSite=none etc) but I am not able to get this to work. Do you have any suggestions?
Thanks…

Last edited 1 year ago by Nick
Alkama Hasan
Alkama Hasan
1 year ago

Hey John thanks for the library i am new to dotnet but really enjoyed to read your article and love to implement i have a problem like how we can configure Authpermission library with customeIdentityUser which is inherited from identity user. like i went through the example2webApi which is configured for IdentityUser but have seen there is some function available for custom identity user too but still there is some issue with it if you provide some function or update in example two it will be better

Last edited 1 year ago by Alkama Hasan
Bill noel
Bill noel
1 year ago

Wow. Wish I had found this a while back. Very well done. I’m now trying to decide whether to toss away my work and use this, or soldier on with what I’ve got.

I am trying to set a global query filter based on your example. I’m using Azure B2C and then using an IClaimsTransformation implementation to add the permission claims I need.

What is happening, though, is the constructor for the DbContext is getting called before the claims transformation runs. So I can’t inject the tenant id into the DbContext because I don’t have it yet.

Any ideas on how to get the ClaimsPrincipal updated before the DBContext constructor? I’m using AutoFac for DI. Great article, also saw the latest .Net Community Standup. Very useful stuff.

Thank you.

Bill noel
Bill noel
1 year ago
Reply to  Bill noel

Wait. I may have found it. I’m using the same DBContext inside the ClaimsTransformation implementation. Which means it’s being constructed before my claims transformation executes. Oof. Can’t test it now, I’ll let you know.

Bill noel
Bill noel
1 year ago
Reply to  Jon P Smith

Thanks for the additional insight and reply. I was texting you from an airport lounge in Honduras when the answer popped into my head.

I’ll be back at my regular workstation in a week or so and let you know how your sound advice worked out.

Julio
Julio
1 year ago

Hi Jon

public

enum

Example4Permissions :

ushort

//Must be ushort to work with AuthP

{

    

NotSet = 0,

//error condition

    

//Here is an example of very detailed control over the selling things

    

[Display(GroupName =

"Sales"

, Name =

"Read"

, Description =

"Can read any sales"

)]

    

SalesRead = 20,

    

[Display(GroupName =

"Sales"

, Name =

"Sell"

, Description =

"Can sell items from stock"

)]

    

SalesSell = 21,

    

[Display(GroupName =

"Sales"

, Name =

"Return"

, Description =

"Can return an item to stock"

)]

    

SalesReturn = 22,

    

//other Permissions left out…

    

//Useful for setting up a SuperAdmin user

    

[Display(GroupName =

"SuperAdmin"

, Name =

"AccessAll"

,

        

Description =

"This allows the user to access every feature"

, AutoGenerateFilter =

true

)]

    

AccessAll =

ushort

.MaxValue,

}

Continuing with this example, if I wanted to add a “global” permission for “Sales”, in order to assign that single permission, to a Sales Administrator, without having to assign each one of the permissions individually, is it possible? any ideas? Or would I have, as I said, assign the three previous permissions… when I could only assign one permission.

[Display(GroupName =

"Sales"

, Name =

"All"

, Description =

"All permissions for Sales"

)]

    

SalesReturn = 29,

Excuse me for my english

Thanks for your time

Julio
Julio
1 year ago
Reply to  Jon P Smith

Thank you very much

Jonathan
Jonathan
1 year ago

What a great library !
I wonder if there’s a way to avoid the generation of AspNetCore.Identity.ApplicationCookie?

I know it not being used afterwards, but it does not feel right sending unnecessary auth related data.
I was looking for a way to “undo” the built in “addCookie” that comes with AddIdentity

Jonathan
Jonathan
1 year ago
Reply to  Jon P Smith

Hi Jon,

Thank you for the prompt reply!
I actually followed your documentation and using JWT token. Nevertheless, a cookie is still being being generated and sent back to the client whenever a user is signed in.
The cookie is not used for further authentication or authorization in subsequent requests, but is still keeps traveling and I was looking for a way to eliminate that completely.

Jonathan
Jonathan
1 year ago
Reply to  Jon P Smith

Hi,

That’s exactly the case.
I actually know where it is coming from (but haven’t figured out how to avoid that).

aspnetcore .AddIdentity is calling internally to .AddCooike

I’m looking for a way to “remove cookie” after calling .AddIdentity.
any tip would be great 🙂

Jonathan
Jonathan
1 year ago
Reply to  Jon P Smith

Thank you!

Jo N
Jo N
1 year ago

I really love your on AuthPermissions.AspNetCore.
I made an API that uses the refresh tokens and tested it with Swagger.
Do you have example code how I can access this API from an MVC project, and how I can ask a new token when it expires?
Thanks

Andres
Andres
1 year ago

Hi Jon
You have valued the option to include the use of http request routes, request body and query chain parameters for authorization in your library. 
Thank you very much.

Andres
Andres
1 year ago
Reply to  Jon P Smith
Andres
Andres
1 year ago
Reply to  Jon P Smith

Thanks for your time

Julio
Julio
1 year ago

Hi Jon
I really love your articles. I know you have planned a version for PostgreSQL. When do you plan to release this update? It would be very useful, so you don’t have to rewrite a lot of code.
Thank you very much.

Julio
Julio
1 year ago
Reply to  Jon P Smith

OK
I need multi-tenants too, and I have learned a lot from your articles.
I am waiting for the new version of the library with Postgresql for my projects.
Thanks for your time

Frank Van der Geld
Frank Van der Geld
1 year ago

Hi,

I really love your work and this is a real time saver. Is it somehow possible to use your library as a sort of identity provider for other .net core webapi’s and for example angular frontends connected to these webapi’s?

With kind regards,

Frank

Frank Van der Geld
Frank Van der Geld
1 year ago
Reply to  Jon P Smith

Thank you for your fast reply! I would indeed try to use it as a IdentityServer4 kind of way. I will investigate if i can create an separate .net core library (with the permissions) which i can share between my webapi’s and the separate ‘identity application’ so the same permissions enum will be available for both applications with the correct versioning.

Alain d'Espaignet
Alain d'Espaignet
2 years ago

I get the separation between Roles and Permissions. But is it not just moving the problem of hardcoding to the PermissionEnum level? Isn’t the code still littered with PermissionEnum.XYZ all over? How can it be made more dynamic?

fatihozturk
2 years ago

Thank you for sharing this article and opensource solution. Authentication and authorization processes  are always time-consuming and an issue that needs attention. We have a subject that is open to development. QR Code sign in, passwordless (for example shazam), select with tenant.

JACQUES MICHEL HACHE
JACQUES MICHEL HACHE
2 years ago

Does it work with MySql?

Adam David Collings
2 years ago

Thank you for creating and sharing this. It is very timely for me, especially the multi-tenant stuff.

Jeff Bowman
Jeff Bowman
2 years ago

Great job, thanks.

Can this be used in an MVC5 application?