I have written a series about a better way to handle authorization in ASP.NET Core which add extra ASP.NET Core authorization features. Things like the ability to change role rules without having to edit and redeploy your web app, or adding the ability to impersonate a user without needing their password. This series has quickly become the top articles on my web site, with lots of comments and questions.
A lot of people like what I describe in these articles, but they have problems extracting the bits of code to implement the features this article describes. This article, with its improved PermissionAccessControl2 repo, is here to make it much easier to pick out the code you need and put it into your ASP.NET Core application. I then give you a step by step example of selecting a “better authorization” feature and putting it into a ASP.NET Core 2.0 app.
I hope this will help people who like the features I describe but found the code really hard to understand, and here is a list of all the articles in the series so that you can find information on each feature.
- Part 1: A better way to handle authorization in ASP.NET Core.
- Part 2: Handling data authorization ASP.NET Core and Entity Framework Core.
- Part 3: A better way to handle authorization – six months on.
- Part 4: Building robust and secure data authorization with EF Core.
- Part 5: A better way to handle authorization – refreshing user’s claims
- Part 6: Adding user impersonation to an ASP.NET Core web application.
- Part 7: Implementing the “better ASP.NET Core authorization” code in your app (this article).
TL;DR; – summary
- The “better authorisation” series provides a number of extra features to ASP.NET Core’s default Role-based authorisation system.
- I have done a major refactor to the companion PermissionAccessControl2 repo on GitHub to make it easier for someone to copy the code they need for the features they want to put in their code.
- I have also built separate authorization methods for the different combinations of my authorization features. This means it easier for you to pick the feature that works for you.
- I have also improved/simplified some of the code to make it easier to understand.
- I then use a step by step description of what you need to do to add a “better authorization” feature/code into your ASP.NET Core application.
- I have made a copy of the code I produced in these steps available via the PermissionsOnlyApp repo in GitHub.
Setting the scene – the new structure of the code
Over the six articles I have added new features on top of the features that I already implemented. This meant the final version in the PermissionAccessControl2 repo was really complex, which made it hard work for people to pick out the simple version that they needed. In this big refactor I have split the code into separate projects so that its easy to see what each part of the application does. The diagram below shows the updated application projects and how they link to each other.

This project may look complex, but many of these projects contain less than ten classes. These projects allow you to copy classes etc. from the projects that cover the features you want, and ignore projects that cover features you don’t want.
The other problem area was the database. Again, I wanted the classes relating to the added Authorization code kept separate from the classes I used to provide a simple, multi-tenant example. This let me to have a non-standard database that was hard to create/migrate. The good news is you only need to copy over parts of the ExtraAuthorizeDbContext DbContext into your own DbContext, as you will see in the next sections.
The Seven versions of the features
Before I get into the step by step guide, I wanted to list the seven different classes that provide different mixes of features that you can now easily pick and choose.
In fact in the PermissionAccessControl2 application allows you to configure different parts of the application so that you can try different features without editing any code. The feature selection is done by the “DemoSetup” section in the appsettings.json file. The AddClaimsToCookies class reads the “AuthVersion” value on startup to select what code to use to set up the authorization. Here are the seven classes that can be used, with their “AuthVersion” value.
- Applied when you log in (via IUserClaimsPrincipalFactory>
- LoginPermissions: AddPermissionsToUserClaims class.
- LoginPermissionsDataKey: AddPermissionsDataKeyToUserClaims class.
- Applied via Cookie event
- PermissionsOnly: AuthCookieValidatePermissionsOnly class
- PermissionsDataKey: AuthCookieValidatePermissionsDataKey class
- RefreshClaims : AuthCookieValidateRefreshClaims class
- Impersonation : AuthCookieValidateImpersonation class
- Everything : AuthCookieValidateEverything class
Having all these version makes it easier for you to select the feature set that you need for your specific application.
Step-by-Step addition to your ASP.NET Core app
I am now going to take you through the steps of copying over the most useful asked for in my “better authorization” series – that is the Roles-to-Permissions system which I describe here in the first article. This gives you two features that aren’t in ASP.NET Core’s Roles-based authorization, that is:
- You can change the authorization rules in your application without needing to edit code and re-deploy your application.
- You have simpler authorization code in your application (see section “Role authorization and its limitations” for more on this).
I am also going to use the more efficient UserClaimsPrincipalFactory method (see this section of article 3 for more on this) of adding the Permissions to the user’s claims. This add the correct Permissions to the user’s claims when thye log in.
And because .NET Core 3 is now out I’m going to show you how to do this for a ASP.NET Core 3.0 MVC application.
NOTE: I have great respect for Microsoft’s documentation, which has become outstanding with the advent of NET Core. The amount of information and updates on NET Core was especially good – fantastic job everyone. I also have come to love Microsoft’s step-by-step format which is why I have tried to do the same in this article. I don’t think I made it as good as Microsoft, but I hope it helps someone.
Step 1: Is your application ready to take the code?
Firstly, you must have some form of authorisation system, i.e. something that checks the user is allowed to login. It can be any for of authorization system (my original client used Auth0 with the OAuth2 setup in ASP.NET Core). The code in my example PermissionAccessControl2 repo has some approaches that work with most authorisation, but some of the more complex ones only work with system that store claims in a cookie (although the approach could be altered to tokens etc.).
In my example I’m going to go with a ASP.NET Core 3.0 MVC app with Authentication set to “Individual User Accounts->Store user accounts in-app”. This means there is database added to your application and be default it uses cookies to hold the logged-in user’s Claims. Your system may be different, but with this refactor its easier for you to work out what code you need to copy over.
Step 2: What code will we need for PermissionAccessControl2 repo?
You need to start out by deciding what parts of the PermissionAccessControl2 projects you need. This will depend on what features you need. Because the projects having names that fit the features this makes it easier to find things.
In my example I’m only going to use the core Roles-to-Permissions feature so I only need the code in the following projects:
- AuthorizeSetup: just the AddPermissionsToUserClaims class
- FeatureAuthorize: All the code.
- DataLayer: A cut-down the ExtraAuthorizeDbContext DbContext (no DataKey, no IAuthChange) and some, but not all, of the classes in the ExtraAuthClasses folder.
- PermissionParts: All the code.
That means I only need to look at about 50% of the projects in the PermissionAccessControl2 repo.
Step 3: Where should I put the PermissionAccessControl2 code in my app?
This is an architectural/style decision and there aren’t any firm rules here. I lean towards separating my apps into layers (see the diagram in this section of an article on business logic). There are lots of ways I could do it, but I went for a simple design as shown below.

Step 4: Moving the PermissionsParts into DataLayer
That was straightforward – no edits needed and no NuGet packages needed to be installed.
Step 5: Moving the ExtraAuthClasses into DataLayer
The ExtraAuthClasses contain code for all features, which makes this part the most complex step as you need to remove parts that aren’t used by features you want to use. Here is a list of what I needed to do.
5.a Remove unwanted ExtraAuthClasses.
I start by removing and ExtraAuthClasses that I don’t need because I’m not using those features. In my example this means I delete the following classes
- TimeStore – only needed for “refresh claims” feature.
- UserDataAccess, UserDataAccessBase and UserDataHierarchical – these are about the Data Authorize feature, which we don’t want.
5.b Fix errors
Because I didn’t copy over projects/code for features I don’t want then some things showed up as compile errors, and some just need to be changed/deleted.
- Make sure the Microsoft.EntityFrameworkCore NuGet package has been added to the DataLayer.
- You will also need the Microsoft.EntityFrameworkCore.Relational NuGet package in DataLayer for some of the configuration.
- Remove the IChangeEffectsUser and IAddRemoveEffectsUser interfaces from classes – they are for the “refresh claims” feature.
- Remove any using statements that don’t link to left out/moved projects.
NOTE: I used some classes/interfaces from my EfCore.GenericServices library to handle error handling in some of the methods in my ExtraAuthClasses. But I am building this app three days after the release of NET Core 3.0 and I haven’t (yet) updated GenericServices to NET Core 3.0 so I added a project called GenericServicesStandIn to host the Status classes.
5.b Adding ExtraAuthClasses to your DbContext
I assume you will have a DbContext that you will use to access the database. Your application DbContext might be quite complex, but in my example PermissionOnlyApp I started with a basic application DbContext as shown below.
public class MyDbContext : DbContext
{
//your DbSet<T> properties go here
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Your configuration code will go here
}
}
Then I needed to add the DbSet<T> for the ExtraAuthClasses I need, plus some extra configuration too.
public class MyDbContext : DbContext
{
//your DbSet<T> properties go here
//Add the ExtraAuthClasses needed for the feature you have selected
public DbSet<UserToRole> UserToRoles { get; set; }
public DbSet<RoleToPermissions> RolesToPermissions { get; set; }
public DbSet<ModulesForUser> ModulesForUsers { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//Your configuration code will go here
//ExtraAuthClasses extra config
modelBuilder.Entity<UserToRole>().HasKey(x => new { x.UserId, x.RoleName });
modelBuilder.Entity<RoleToPermissions>()
.Property("_permissionsInRole")
.HasColumnName("PermissionsInRole");
}
}
Now you can replace some of the references to ExtraAuthorizeDbContext references in some of the ExtraAuthClasses.
Step 6: Move the code into the RolesToPermissions project
Now we need to move the last of the Roles-to-Permissions code into the RolesToPermissions project. Here are the steps I took.
6a. Move code into this project from the PermissionAccessControl2 version.
What you move in depends on what features you want. If you have some of the complex feature like refresh user’s claims on auth changes, or user impersonation you might want to put each part in a separate folder. But in my example, I only need code from one project and pick one class from another. Here is what I did.
- I moved all the code in the FeatureAuthorize project (including the code in the PolicyCode folder).
- I then copied over the specific setup code I needed for the feature I wanted I this case this was the AddPermissionsToUserClaims class.
At this point there will be lots of errors, but before we sort these out you have to select the specific setup code you need.
6b. Add project and NuGet packages needed by the code
This code accessing a number of support classes for it to work. The error messages on “usings” on in the code will show you what is missing. The most obvious is this project needs a reference to the DataLayer. Then there are NuGet packages – I only needed three, but you might need more.
6c. Fix references and “usings”
At this point I still have compile errors, either because the applciation’s DbContext is a different name, and because it has some old (bad) “usings”. The steps are:
- The code refers to ExtraAuthorizeDbContext, but now it needs to reference your application DbContext. In my example that is called MyDbContext. You will also need to add a “using DataLayer” to reference that.
- You will have a number incorrect “using” at the top of various files – just delete them.
Step 7 – setting up the ASP.NET Core app
We have added all the Roles-to-Permission code we need, but the ASP.NET Core code doesn’t use it yet. In this section I will add code to the ASP.NET Core Startup class to link the AddPermissionsToUserClaims into the UserClaimsPrincipalFactory. Also I assume you have a database your already use to which I have add the ExtraAuthClasses. Here is the code that does all this
public void ConfigureServices(IServiceCollection services)
{
// … other standard service registration removed for clarity
//This registers your database, which now includes the ExtraAuthClasses
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DemoDatabaseConnection")));
//This registers the code to add the Permissions on login
services.AddScoped<
IUserClaimsPrincipalFactory<IdentityUser>,
AddPermissionsToUserClaims>();
//Register the Permission policy handlers
services.AddSingleton<IAuthorizationPolicyProvider,
AuthorizationPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
}
NOTE: In my example app I also set the options.SignIn.RequireConfirmedAccount to false in the AddDefaultIdentity method that registers ASP.NET Core Indentity. This allow me to log in via a user I add at startup (see next section). This demo user won’t have its email verified so I need to turn off that constraint. In a real system you might not need that.
At this point all the code is linked up, but we need an EF Core migration to add the ExtraAuthClasses to your application DbContext. Here is the command I run in Visual Studio’s Package Manager Console – note that it has to have extra parameters because there are two DbContexts (the other one is the ASP.NET Idenity ApplicationDbContext).
Add-Migration AddExtraAuthClasses -Context MyDbContext -Project DataLayer
Step 8 – Add Permissions to your Controller actions/Razor Pages
All the code is in place so now you can use Permissions to protect your ASP.NET Core Controller actions or Razor Pages. I explain Roles-to-Permissions concept in detail in the first article, so I’m not going to cover that again. I’m just going to show you how a) to add a Permission to the Permissions Enum and then b) protect an ASP.NET Core Controller action with that Permission.
8a. Edit the Permissions.cs file
In the code you copied into the PermissionsParts folder in the DataLayer you will find a file called Permissions.cs file, which defines the enum called “Permissions”. It has some example Permissions which you can remove, apart for the one named “AccessAll” (that is used by the SuperAdmin user). Then you can add the Permission names you want to use in your application. I typically I give each Permission a number, but you don’t have to – the compiler will do that for you. Here is my updated Permissions Enum.
public enum Permissions : short //I set this to short because the PermissionsPacker stores them as Unicode chars
{
NotSet = 0, //error condition
[Display(GroupName = "Demo", Name = "Demo", Description = "Demo of using a Permission")]
DemoPermission = 10,
//This is a special Permission used by the SuperAdmin user.
//A user has this permission can access any other permission.
[Display(GroupName = "SuperAdmin", Name = "AccessAll", Description = "This allows the user to access every feature")]
AccessAll = Int16.MaxValue,
}
8b. Add HasPermission Attribute to a Controller action
To protect an ASP.NET Core Controller action or Razor Page you use the “HasPermission” attribute to them. Here is an example from the PermissionOnlyApp.
public class DemoController : Controller
{
[HasPermission(Permissions.DemoPermission)]
public IActionResult Index()
{
return View();
}
}
For this action to be executed the caller must a) by logged in and b) either have the Permission “DemoPermission” in their set of Permissions, or be the SuperAdmin user who has the special Permission called “AccessAll”.
At this point you are good to go apart from creating the databases.
Step 9 – extra steps to make it easier for people to try the application
I could stop there, but I like to make sure someone can easily run the application to try it out. I therefore am going to add:
- Some code in Program to automatically migrate both databases on startup (NOTE: This is NOT the best way to do migrations as it fails in certain circumstances – see my articles on EF Core database migrations).
- I’m going to add a SuperAdmin user to the system on startup if they aren’t there. That way you always have a admin user available to log in on startup – see this section from the part 3 article about the SuperAdmin and what that user can do.
I’m not going to detail this because you will have your own way of setting up your application, but it does mean you can just run this application from VS2019 and it should work OK (it does use the SQL localdb).
NOTE: The first time you start the application it will take a VERY long time to start up (> 25 seconds on my system). This is because it is applying migrations to two databases. The second time will be much faster.
Conclusion
Quite a few people have contacted me with questions about how they can add the features I described in these series into their code. I’m sorry it was so complex and I hope this new article. I also took the time to improve/simplify some of the code. Hopefully this will make it easier to understand and transfer the ideas and code that goes with this these articles.
All the best with your ASP.NET Core applications!
Thanks a lot for wonderful article. I was wondering how can we implement requirements like where in hierarchy user can access selected children data rather than all children’s’ data. For example a user created at Sub group which has 3 retail outlets and we want to restrict user to access data of only 2 Retail outlets out of 3.
Hi Rishi,
Anything is possible. For instance you could add a extra Where clause that only returned the data for the 2 retail outlets. The only problems are: a) performance – it will be slower and b) you have to also filter any related data (e.g. anything loaded via an Include won’t be filtered unless you add a Where clause in the Include method.
PS. You might be interested that I have now created a library called AuthPermissions.AspNetCore (AuthP for short) which implements most of the features described in this series of articles, and adds extra feature since EF Core / ASP.NET Core have progressed.
Hi Jon Thanks for reply. I was wondering if like data key which is very powerful in terms of performance as well, we can implement. I understood you point, i have to further filter data using where clause and one to many mapping (User-Tenants)
You cannot use a query filter as it is configured on first use and you can’t change it. From a performance point the best approach is to turn off the query filter via the IgnoreQueryFilter method and then add a where clause with the DataKeys of the retail outlets, e.g.
This line not working for me.
Can any one help me for this?
I got this error:
Some services are not able to be constructed (Error while validating the service descriptor
‘ServiceType: Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory
1[Microsoft.AspNetCore.Identity.IdentityUser]
1[Microsoft.AspNetCore.Identity.IdentityUser]’Lifetime: Scoped ImplementationType: FXV_App.AuthorizeSetup.AddPermissionsToUserClaims':
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager
while attempting to activate ‘My_App.AuthorizeSetup.AddPermissionsToUserClaims’.)
InvalidOperationException: Unable to resolve service for type ‘Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]’
while attempting to activate ‘My_App.AuthorizeSetup.AddPermissionsToUserClaims’.
Hi Nileksh,
I’m really not sure what that is, but I found a issue that looks like it covers your problem – have a look at this link.
The other thing you might be interested in is my new library called AuthPermissions.AspNetCore. This implements many of the features from this series – see this new article called “Finally, a library that improves role authorization in ASP.NET Core“. Version 1.1.0 is out, which gives you the Roles/Permissions feature.
Hi thanks for the reply I fixed that issue. Now I have another issue. When I logged in it shows 403 error when I permission attribute to my controller action.
After user found I used JWT mechanism to login and manage claims.
Here is the function:
I think claim add process is not working.
Can you please help me why this issue occur?
Your code isn’t anything to do with what the article is about. For general code questions I suggest you ask a question on stack overflow.
I am getting same issue, can you please tell me how you resolve this?
This stack overload answer has the answer, with a link to the Microsoft documentation with an example.
If you are using the AuthP library and building Web API application, then you need to add the JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear() to your startup code – click this link to see this code in the AuthP example Web API app.
Hi
Thanks for the great series.
I’m still making my way through the articles(on Part 2). I’m new to Asp.Net Core and eyeing it for a new OSS project as I’m a bit of a dotnet fanboy.
My needs for authorization will be more complex like yours and I think I can sum them up by saying I need to create and RBAC(ABAC?) based ACL system like AWS/GCP/Azure IAM or Kubernetes/OpenShift.
My experience researching this thus far has lead to some very mixed feelings about asp.net core auth. The interfaces seem to be missing basic stuff like looking up the requirements for a user/group/role/policy(for optimizing data access and other stuff in my case).
It seems like with all the additional work required just replacing the entire system would be a tiny fraction of the overall effort. I’m curious what I would be missing out on if I choose to create my own auth service and interfaces that more closely match my requirements?
Hi Greg,
Glad you like dotnet – I do too!
There are many ways to set up authentication in ASP.NET Core, that is confirming that the user is allowed to log in. My articles are mainly about authorization, which is about what the logged user can do once they have been authenticated.
The demo code uses a local database for authentication because that’s simple and it will work on your machine, but the approaches I cover in these articles works for any authentication approach (the client system where I build the first version used OAuth for authentication).
If you want to build something for yourself I would recommend using a existing authentication approach, as authentication is the hard part. Once you have a validated, logged in user there are lots of ways to handle the authorization – Part 3 gives info on some simple approaches and later parts provide more complex versions.
Have fun.
Hi
thank you for the great article.
I have one problem when trying to implement your solution.
I want to use everything up to the impersonation feature. I think I have implemented everything correct. The only problem is that now I cant logout.
I have traced the problem back to the startup code.
The Problem seems to be in the line
If I have both lines in my code, it wont call the SignOutAsync Method specified here. If I comment the first line out, it will call it.
Both happens even if I dont tell it to impersoante someone.
Do you have an idea of what is happening here?
Hi Simon,
I have just tried the original PermissionAccessControl2 code and it works OK for logout. Your code after the call to SigningOutAsync looks a bit odd – why are you changing the Cookie Name and timespan if you are logging out?? That code isn’t in the PermissionAccessControl2
Try running the PermissionAccessControl2 and appsetting.json DemoSetup.AuthVersion to “PermissionsDataKey” to mimic the setup you are trying to create.
The original code works just fine.
the name and timespan should only be changed in the setup not everytime the user logs out.
Might the issue be that i dont have setup permissions/roles yet?
My problem most likely is my cookie auth setup. do you know a good article for that?
I changed line 51 in AuthCookieValidateImpersonation.cs from
Is it possible that there is a version problem? Im using Microsoft.AspNetCore.Identity.Core Version 5.0.5
I tried everything i could find to set AuthenticationScheme to Cookie and nothing works
I just updated PermissionAccessControl2 to net5.0 and it worked fine, so I don’t think its that (very easy to do – it doesn’t alter any of my code).
I suggest you just create a very simple ASP.NET Core application and add a call to
UserClaimsPrincipalFactory<IdentityUser>
(see this part of the part 3 article) and add dummy claim to check everything is working. That will get you starting point as at the moment you don’t know what is wrong. From there you can try adding more code (say a method that lists the user’s claims) so that you are sure the basics are correct.PS.
If you haven’t set up some part the the permissions needs, then you should get an exception when it tries to set up the permissions claim. It doesn’t sound like that is you problem.
Thank you for such an excellent series of articles, your hard work and sharing of the code.
A quick question….
For multi-tenant, am I correct in thinking, the overall objective is for data rows in newly added child tables implementing IShopLevelDatakey e.g. Stock, to be ‘owned’ by their parent TenantBase e.g. RetailOutlet rather than the user that created them? So, especially at create time, the child data row should be marked with the Datakey for the TenantBase/RetailOutet parent rather than the Datakey in the User’s Claims? All users with a hierarchial Datakey for that RetaiOutlet or above should then have data row visibility of the child rows?
The current version of MarkWithDataKeyIfNeeded uses the Datakey sourced from the user’s claim (via CompanyDbContext constructor) to set the DataKey on any IShopLevelDataKey rows during SaveChanges(). That coincidently works if the current user is tentant-linked to the parent RetailOutet but not if they are tenant-linked to a SubGroup or Company.
My current thought is to use the controller/page action to set the new child row datakey via ‘SetShopLevelDataKey(parentRetailOutlet.DataKey)’ and modify MarkWithDataKeyIfNeeded() to only call SetShopLevelDataKey() if the DataKey is not already set…..
public static void MarkWithDataKeyIfNeeded(this DbContext context, string accessKey)
{
//at startup access key can be null. The demo setup sets the DataKey directly.
if (accessKey == null)
return;
foreach (var entityEntry in context.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added))
{
if ( (entityEntry.Entity is IShopLevelDataKey shopItem) && (shopItem.DataKey == null))
shopItem.SetShopLevelDataKey(accessKey);
}
}
Does this make sense or would you have a better suggestion?
Not really a quick question but I’ll try to answer your question :).
Their are lots of multi-tenant types:
Now, you are talking about the “IShopLevelDatakey”, “Stock”, which comes from Part 4 or the series, and that is Hierarchical multi-tenant where each user has an access key, which is held in a claim. THIS IS A COMPLEX EXAMPLE!
Users at the shop level can read and write information related to the shop, but managers (i.e. users above the shop level) can only read the data (see this section of Part 4 on that). So, to your questions.
No, it uses the access key of the user. If they are a shop person it works. If they are a manager it throws an exception because I detect that their access key is not suitable for writes.
As I said, my design doesn’t do that, but of course you could change it. It gets complex if you want managers to add stock etc. as they need to pick the shop to work on, which creates a new level of indirection. Its doable, but hard.
I hope that helps.
I have an API and an Authentication server with Identity Server 4 installed, but I’m not entirely sure where to place some of these files. Because my login takes place on the auth server and deals out tokens and then there are the controller routes on the API that needs protecting, both projects need access to most of the classes and enums. For example Permissions.cs is needed in both the API and the Auth Server. How would you go on about this? Is it okay to have duplicate Permissions.cs?
I’m not sure because I haven’t used Identity Server. I would ask why the Permissions.cs couldn’t be in project that both your API and the Identity Server? The Permissions.cs doesn’t rely on anything else so its easy to make it at the bottom of the projects.
If API and the Identity Server are they two completely different solutions, then that won’t work. You could copy the class but that’s bound to cause problems at some point when you forget to copy it.
Sorry I can’t help any more.
This looks really great. Thanks for all your effort.
Where would I hook in a distributed cache, like redis so that I can run multiple instances of the UI behind a load balancer?
Hi Nina,
Firstly the claims, which is what holds the authorization data, are held either a cookie or a token (this code uses cookies) so they don’t need caching.
The only place where a cache would help is if you wanted to implement the refreshing user’s claims in part 5. In that implementation it has to check the database on every HTTP request.
I hope that helps.
Hi Jon, in C#, we have enum flag feature to have enum as a combination of enum values. Why dont we use this to store the permissions linked with role in database instead of a string? I think we can also do that with permission claim in user’s claims.
Hi Vinh again,
Yes, using a enum flag would be great, but that limited you to 64 different enums and I wanted more than that. Therefore I used individual enums and packed them into a string in the claim. That made them small which means a Cookie could handle thousands of permissions for each user.
I did an enum flag (long) for handing paid-for features because I though 64 features was enough. You can see this using the link below.
https://www.thereformedprogrammer.net/a-better-way-to-handle-authorization-in-asp-net-core/#how-to-handle-optional-paid-forfeatures
Thanks for you answer, Jon. Actually, i thought about the limitation of enum flag on my way home 😀
Hello, it is such a great series that is really help for me when searching for a authorization approach that can fit my project context. I have one question for you when reading your articles. You are using cookie authentication in your example as well as explaination. You can use OnValidatePrincipal to update the cookie if there is any case such as: out of date permissions, impersionation change… But if i use bearer authentication using JWT format for token, how can i alert client about new token that it needs to get for next request? I thought about a solution is that i will add a header value in the response that the client will check to decide if it needs to renew the token to get updated permission claims or impersonated claims data. Can you check my case?
Hi Vinh,
Yes, your approach sounds correct.In Part 5 where I change a user’s claims dynamically I also talked about doing that when using a JWT token. I found blinkingcaret’s article on using token to do this very helpful – see link below.
https://www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/
Hi Chad,
I have written a very simple list of users with roles and datakey, and a list of the roles with their permissions (just run the app and click on the Users -> All Users or Users -> All Roles. But that is just to let you see how the example app is set up.
It shouldn’t be too hard to write a user/roles maintenance views, but that is up to you to fit your application.
Hello. Great article! I was wondering if you have provided, or plan to provide, the views or razor pages to manage the new features? Particularly, the roles to permissions, permissions, and users to roles maintenance.
Thanks!
This article series has been a great help to me when setting up a permissions-based system, though I’ve only used a small part of it. Thank you for writing it!
I have two questions:
1) If I want a method to require more than one permission, would I simply add the [HasPermission] attribute multiple times to that method?
2) I would like to enrich the 403 errors so that people using my API can get a better understanding of why they were denied access to a method. Is there a simple way to override the default 403 errors produced by this attribute so that I can replace them with my own, enriched versions? I have no trouble enriching errors in regular code, but it’s not quite as straightforward when using attributes.
Hi Espen,
Thanks for your kind words. Glad you found the articles useful. Here are my answers to your questions.
1) “Can I use multiple [HasPermission] attributes on one method”.
I don’t think you can because the ASP.NET Core AuthorizationPolicyProvider takes in one string, which is the policy name. You should think of the permissions as a per-feature, or per-method key which you can use in multiple Roles. That should allow you to come up with a way to only need one permission per method.
2) “Can I change the HTTP return on not authorized?”
Again, its ASP.NET Core Authorization code that returns the 403 HTTP status. There might be a way to do something in ASP.NET Core, but I haven’t looked at that. You should look into ASP.NET Core Authorization in more detail – there may be an answer to that.
I think there are ways to override the behavior of HTTP codes from the basic authentication system, so that should be doable. I just hadn’t considered the fact that this uses the same system as the basic authentication layer.
The reason I asked about multiple attributes is because I’ve seen examples of multiple requirements by adding the attribute multiple times, but I have never tried this for myself. I wasn’t thinking about doing it like [HasPermission(Permissions.X, Permissions.Y)], but like [HasPermission(Permissions.X)] [HasPermission(Permissions.Y)]. But if you think this won’t work, that’s fine. Multiple permissions for a method would probably indicate a design flaw in my system anyway.
Hi Espen,
Your [HasPermission(Permissions.X, Permissions.Y)] example made me think and there may be a way to do this (but read the last paragraph). Having two attributes, [HasPermission(Permissions.X)] [HasPermission(Permissions.Y)] won’t work because the way the policy authentication that would be an AND (I think – I haven’t tried it).
But you could use [HasPermission(Permissions.X, Permissions.Y)] and combine them as a comma delimited string e.g. “X,Y”. You would then need to change the PermissionHandler in directory PermissionAccessControl2/ FeatureAuthorize/ PolicyCode/ to split the “X,Y” into separate permissions and do a OR on the checks. I haven’t tried it but I’m pretty sure it will work.
BUT in the first article one of the main reasons from moving away for Roles is that you have to edit your code if you want to change some of the authorization. The permissions are supposed to be feature-centric, e.g. “can read x” and “can add/update y” and then you use Roles, which are now only in the database, to combine them into human-centric groups, e.g. “User Admin”, “Can Delete Orders”. The Permissions are packed into two bytes so a user can have a thousand permissions and they still fit in a Cookie.
Awesome mate. Thank you!!!
Glad you found this series useful.
… or perhaps integrating Identity Server 4 with your solution 🙂
Hi Jon, thank you a lot for this detailed tutorial. After struggling quite a little I was able to understand it and adopt it to my current project. I have one question concerning the superadmin: running the project it seems that the superadmin doesn’t have rows in the DataAccess table and, because of this, when I log in as the superadmin I am not able to display, for example, Shop Stocks. I think that you did this on purpose because the user impersonation feature comes to help and because each user can have only one Company at a time.
What if, in my project, I need the possibility to allow users (admin users) to be associated to many Tenants at a time? I think I could implement a List of DataKeys associated to the user in order to evaluate what the user can access. In addition it would be nice to have a superadmin user which can access all tenants. I’m not sure if it is more convenient to add DataAccess rows to the superadmin user for every new tenant that is added as well or to create a new Permission or something similar to check in HasQueryFilter method (similar to Permissions.AccessAll). It would be nice to hear your opinion/suggestion about this.
Hi Silvia,
I had to do the same thing for my client, i.e. an admin person could pick a company to access.My solution was crazy difficult, mainly because we were using Auth0 and they wanted all to happen on login.
For you I suggest you use the user impersonation code (see https://www.thereformedprogrammer.net/adding-user-impersonation-to-an-asp-net-core-web-application/ ), but maybe change it a bit. For instance if you created a company admin user that is locked to each company, i.e they have the DataKey for that company, whenever you created a company. Then you could put a front-end on impersonation code to show all the companies, and when they pick one you set them up as impersonating the company admin user for the company they picked.
If you are happy to dig into impersonation code then you can make that simpler – the key point is you need a way to change the DataKey in the current admin user’s claims and persist it to the authorization cookie.
Hi Jon, thank your for the suggestion. I’m going to try with the impersonation code and see what happens.
I appreciate the vast amount of time and effort you have put into this and I’m using part of it in my application to save myself the time of figuring out how to properly store/retrieve permissions, etc. The only “critique” I could offer is, if you’re using .netcore 3.0 and you wanted to refresh claims, you could just call
await userManager.UpdateSecurityStampAsync(user)
after changing a user’s role and then ensure you have the following within within the startup.cs?services.Configure(options =>
{
options.ValidationInterval = TimeSpan.Zero;
});
This would make it so that upon updating a user’s roles, you’d update their security stamp which will log them out instantly and you can just update the claims on login again. This avoids needing a whole system to track when a claim was last refreshed so that it’s only refreshed when it changes.
Hi Brendan,
Thank you for your input. I’m always up for learning better ways to do things. What you describe sounds good, but aren’t there performance issues to setting the ValidationInterval to zero?? See this article https://security.stackexchange.com/questions/167832/asp-net-why-default-securitystamp-validation-interval-is-set-to-30-minutes
That is definitely a fair point! Thank you for linking that post to me. I guess it depends on scale of the application and how many users you have concurrently. I’m currently managing an application that has about ~100 users and isn’t expected to get much higher so I can lower it to a zero or a minute and it’ll be “good enough” for my purposes. Again thank you for your time and effort in making these articles and a “framework” of sorts for easily implementing this into an ASP.Net Core MVC App.
Hey, thanks for the tutorial, but why do you have two different contexts Application & MyDBContext, two different databases for this? Whats the benefit?
Hi kerpeki,
I did it so that it was easier for people to see what classes go with the “better authorization” code – just look at the ExtraAuthorizeDbContext and its associated classes.
In big projects I might use multiple DbContexts to help with building DDD “bounded Contexts”, but it has a number of down sides, like EF Core migrations are very complex and sometimes impossible (I don’t use EF Core migrations in my own projects).
I wouldn’t suggest using multiple DbContexts unless you have good reason to.
Thanks for the super nice explanation.
Have a nice day && Loving your blog posts!
What is the max number of permission I can store per user in consideration of the cookie size? if for example I have a big application and average of thousand or two of permissions is it fine to store them in the cookie and keep it below 4kb of size?
With the coding I used each permission takes 2 bytes. You also have some other claims which are stored in the cookie too, like the user Id. You can work out how much they take by adding the size of the key and value. Don’t forget: you might have thousands of permissions but how many does a single user have – most likely an admin user is the worst case.
For my client I was worried about this and used a more complex permissions setup with a type and then a series of increasing levels of allowed actions, e.g. OrderRead = xx0, OrderCreate = xx1 (which includes Read), OrderUpdate = xx2 (which includes Read and Create), etc. That reduces the permissions a LOT. But in the end we didn’t need it and it made adding permissions complex (I built a tool to build the permission from a json file).
do you mean a permission code “1057” for example consume 2 bytes from the cookie size?
As you mentioned the biggest concern for users like admin and super users and while the application I’m referring to is growing this should be considered from the begging.
Is it possible to share this code with the complex permissions setup?
Hello.
Is it a viable solution to implement IClaimsTransformation and not store claims in cookie/token, instead when a request comes in a IClaimsTransformation service does the permission claims setup and everything else works just the same? Only possible problem is that IClaimsTransformation gets called several times during request processing pipeline, so you would have to take that into account and take necessary precautions.
Hi Jon,
Thanks for the great article, really learned a lot!
You mentioned in the first part and in some comments that you implemented Auth0 and maybe different Front-End and that it was quite complex and challenging.
I am trying to do that now and struggling (React front and WebAPI back).
Can you share any hints of the key difficulties you encountered (article like this would be amazing:))?
Hi Petar,
The project was ASP.NET Core with Angular. The Angular person wanted to handle Auth0, but that wouldn’t work as we needed claims added to the ASP.NET Core’s user. Auth0 had example code that used 0Auth flow (this was about 2 years ago) but a quick look says they are now using OpenId. I’m sure there are other examples too.
The main problem was just understanding Auth0 and and setting up the machine-to-machine part. Like most complex things like this you have to chip away at a simple example until that works and then expand it.
All the best with your project!
Thank you. Your project did help me a lot in the part of understanding of how things work.
Auth0 has a concept called Rules and they are simple JS functions that you can use to add claims to the token just like you showed.
I guess other providers have similar concepts.
That way I get a ready token and don’t have to do anything in the application itself.
Looks very promising!
Thank you!!!
Hi Jon,
So if i understood correctly, if i’m using .NET Core Identity is:
UserToRoles equivalent to AspNetUserRoles
RolesToPermissions equivalent to AspNetUserRoles?
In ASP.NET Core identity you only have the Roles of a user – the AspNetUserRoles.But you don’t have a RolesToPermissions equivalent because the Roles are defined in the [Authorize] attribute, e.g. [Authorize( Roles = “Manager,Admin”)].
I suggest you read the very first article at https://www.thereformedprogrammer.net/a-better-way-to-handle-authorization-in-asp-net-core/ which explains what the normal use of Roles in ASP.NET Core does, and how this approach gives you the ability to change what a user can access dynamically, rather than having to edit your code and re-publish it.
Yea i understand that part, it’s just difficult to go from PermissionsAccessControl2 to a simple expansion of .NET Core identity within the same database, i was wondering how the tables would look like. But i don’t want to criticize, i guess you can’t please everyone.
Hi bdcp,
I’m not sure I understand what you are trying to do – are you trying to combine the class/tables in my extra database into the ASP.NET Core individual users account database? You could do that, but I kept them apart in the example application so that people could see what the different parts do.