Part 3: A better way to handle ASP.NET Core authorization – six months on

Last Updated: July 31, 2020 | Created: July 2, 2019

About six months ago I wrote the article “A better way to handle authorization in ASP.NET Core” which quickly became the top article on my web site, with lots of comments and questions. I also gave a talk at the NDC Oslo 2019 conference on the same topic which provided even more questions. In response to all the questions I have developed a new, improved example application, called PermissionAccessControl2 (referred to as “version 2”) with series of new articles that cover the changes.

The version 2 articles which cover improvements/changes based on questions and feedback.

UPDATE: See my NDC Oslo 2019 talk which covers these three articles.

Original articles:

NOTE: Some people had problems using the code in the original web application (referred to a version 1) in their applications, mainly because of the use of in-memory databases. The version 2 example code is more robust and supports real databases (e.g. SQL Server).

  • This article answers comments/questions raised by the first version and covers the following changes from the original feature authorization article. If you are new to this topic the original article is a better place to start as it explains the whole system.
  • The changed parts covered in this article are:
    • : Since the first article I have found a much simpler way to setup the users claims on login if you don’t need a refresh of the users claims.
    • Roles: In the original article I used ASP.NET Core’s identity Roles feature, but that adds some limitations. The version 2 example app has its own UserToRole class.
    • Using Permissions in the front-end: I cover how to use Permissions in Razor pages and how send the permissions to a JavaScript type front-end such as AngularJS, ReactJS etc.
    • Provides a SuperUser: Typically, a web app needs a user with super-high privileges to allow them set up a new system. I have added that to the new example app.
  • The version 2 example app is a super-simple SaaS (Software as a Service) application which provides stock and sales management to companies with multiple retail outlets.

Setting the scene – my feature/data authorization approach

As I explained in the first article in this series, I was asked to build a web application that provided a service to various companies that worked in the retail sector. The rules about what the users could access were quite complex and required me to go beyond the out-of-box features in ASP.NET Core. Also, the data was multi-tenant and hierarchical, i.e. each company’s data must be secured such that a user the data they are allowed to access.

Many (but not all) of the issues I solved for my client are generally useful so, with the permission of my client, I worked on an open-source example application to capture the parts of the authentication and authorization features that I feel are generally useful to other developers.

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

Below is a figure showing the various parts/stages in my approach to feature/data authorization. I use the ASP.NET Core authentication part, but I replace the authorization stage with my own code.

My replacement authorization stage provides a few extra features over ASP.NET Core Role-based authorization.

  • I use Roles to represent the type of user that is accessing the system. Typical Roles in a retail system might be SalesAssistant, Manager, Director, with other specific-job Roles like FirstAider, KeyHolder.
  • In the ASP.NET Core code I use something I call Permissions, which represent a specific “use case”, such as CanProcessSale, CanAuthorizeRefund. I place a single Permission on each ASP.NET Core pages/web APIs I want to protect. For instance, I would add CanProcessSale Permission to all the ASP.NET Core pages/web APIs than are used in the process sale use case.
  • The DataKey is more complex because of the hierarchical data and I cover all the bases on how to be sure that this multi-tenant system is secure – see see part 4 article.

There are other parts that I am not going to cover in this article as they are covered in other articles in this series. They are

  • Why using Enums to implement Permissions was a good idea (see this section in first article).
  • How I managed paid-for features (see this section in the first article).
  • How I setup and use a DataKey for segregate the data – see the part 4 article.

What the rest of this article does is deal with improvements I have made in the version 2 example application.

A simpler way to add to the User’s Claims

My approach relies on me adding some claims to the User, which is of type ClaimsPrincipal. In the original article I did this by using an Event in ApplicationCookie, mainly because that is what I had to use from my client. While that works I have found a much simpler way that adds the claims on login. This approach is much easier to write, works for Cookies and Tokens, and is more efficient. Thanks to https://korzh.com/blogs/net-tricks/aspnet-identity-store-user-data-in-claims for writing about this feature.

We do this by implementing a UserClaimsPrincipalFactory and registering it as a service. Here is my implementation of the UserClaimsPrincipalFactory.

public class AddPermissionsToUserClaims : 
UserClaimsPrincipalFactory<IdentityUser>
{
    private readonly ExtraAuthorizeDbContext _extraAuthDbContext;

    public AddPermissionsToUserClaims(UserManager<IdentityUser> userManager, 
        IOptions<IdentityOptions> optionsAccessor,
        ExtraAuthorizeDbContext extraAuthDbContext)
        : base(userManager, optionsAccessor)
    {
        _extraAuthDbContext = extraAuthDbContext;
    }

    protected override async Task<ClaimsIdentity> 
        GenerateClaimsAsync(IdentityUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        var userId = identity.Claims
           .SingleOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value;
        var rtoPCalcer = new CalcAllowedPermissions(_extraAuthDbContext);
        identity.AddClaim(new Claim(
            PermissionConstants.PackedPermissionClaimType,
            await rtoPCalcer.CalcPermissionsForUser(userId)));
        var dataKeyCalcer = new CalcDataKey(_extraAuthDbContext);
        identity.AddClaim(new Claim(
            DataAuthConstants.HierarchicalKeyClaimName, 
            dataKeyCalcer.CalcDataKeyForUser(userId)));
        return identity;
    }
}

You can see on line 17 I get the original claims by calling the base GenerateClaimsAsync. I then use the UserId to calculate the Permissions and the DataKey, which I add to the original claims. After this method has finished the rest of the login code will build the Cookie or Token for the user.

To make this work you need to register in the Configure method inside the Startup code using the following code:

services.AddScoped<IUserClaimsPrincipalFactory<IdentityUser>,
    AddPermissionsToUserClaims>();

NOTE: The original code and this approach means that the claims are fixed until the user logs out and logs back in again. In the Part 5 article I show ways to refresh the user’s claims when the roles/permissions have been changed by an admin person.

Adding our own UserToRole class

In the version 1 example app I used ASP.NET Core’s Role/RoleManger for a quick setup. But in a real application I wouldn’t do that as, I only want ASP.NET Core’s Identity system to deal with the authentication part.

The main reason for providing my own user to role class is because you can then do away with ASP.NET Core’s built-in identity database if you are using something like the OAuth 2 API (which my client system used). Also, if you are splitting the authorization part from the authentication part, then it makes sense to have the User-to-Roles links with all the other classes in the authentication part.

Note: Thanks to Olivier Oswald for his comments where he asked why I used the ASP.NET Core’s Role system in the first version. I was just being a bit lazy, so in version 2 I have done it properly.

In the version 2 example app I have my own UserToRole class, as shown below.

public class UserToRole
{
     private UserToRole() { } //needed by EF Core

     public UserToRole(string userId, RoleToPermissions role) 
     {
         UserId = userId;
         Role = role;
     }
 
     public string UserId { get; private set; }
     public string RoleName { get; private set; }

     [ForeignKey(nameof(RoleName))]
     public RoleToPermissions Role { get; private set; }
    
     //… other code left out
}

The UserToRole class has a primary key which is formed from both the UserId and the RoleName (known as a “composite key”). I do this to stop duplicate entries linking a User to a Role, which would make managing the User’s Role more difficult.

I also have a method inside the UserToRole class called AddRoleToUser. This adds a Role to a User with check as adding a duplicate Role to a User will cause a database exception, so I catch that early and sent a user-friendly error message to the user.

Using Permissions in the front-end

The authorization side of ASP.NET returns HTTP 403 (forbidden) if a user isn’t allowed access to that method. But to make a better experience for a user we typically want to remove any links, buttons etc. that the user isn’t allowed to access. So how do I do that with my Permissions approach?

Here are the two ways you might be implementing your front-end, and how to handle each.

1. When using Razor syntax

If you are using ASP.NET Core in MVC mode or Razor Page mode, then you can use my extension method called  UserHasThisPermission. The code below comes from the version 2 _layout.cshtml file and controls whether the Shop menu appears, and what sub-menu items appear.

@if (User.UserHasThisPermission(Permissions.SalesRead))
{
    <li class="nav-item">
        <div class="dropdown">
            <a class="nav-link text-dark dropdown-toggle" role="button"
               id="dropdown1MenuButton" 
               data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                Shop
            </a>
            <div class="dropdown-menu" aria-labelledby="dropdown1MenuButton">
                @if (User.UserHasThisPermission(Permissions.SalesSell))
                {
                    <a class="nav-link text-dark" asp-area="" 
                         asp-controller="Shop" asp-action="Till">Till</a>
                }
                <a class="nav-link text-dark" asp-area="" 
                       asp-controller="Shop" asp-action="Stock">Stock</a>
                @if (User.UserHasThisPermission(Permissions.SalesRead))
                {
                    <a class="nav-link text-dark" asp-area="" 
                         asp-controller="Shop" asp-action="Sales">Sales</a>
                }
            </div>
        </div>
    </li>
}

You can see me using the UserHasThisPermission method on lines 1, 12 and 17.

2. Working with a JavaScript front-end framework

In some web  applications you might use a JavaScript front-end framework such as AngularJS, ReactJS etc. to manage the front-end. In that case you need to pass the Permissions to your front-end system so that you can add code to control what link/buttons are shown to the user.

It is very simple to get access to the current user’s Permissions via the HttpContext.User variable which is available in any controller. I do this via a Web API and here is the code from my FrontEndController in my version 2 application.

[HttpGet]
public IEnumerable<string> Get()
{
    var packedPermissions = HttpContext.User?.Claims.SingleOrDefault(
        x => x.Type == PermissionConstants.PackedPermissionClaimType);
    return packedPermissions?.Value
        .UnpackPermissionsFromString()
        .Select(x => x.ToString());
}

This action returns an array of Permission names. Typically you would call this after a login and store the data in a SessionStorage for use while the user is logged in. You can try this in the version 2 application – run the application locally and go to http://localhost:65480/swagger/index.html. Swagger will then display the FrontEnd API which has one command to get the user’s permissions.

NOTE: In part 5, where the permissions can change dynamically I show a way that the front-end can detect that the permissions have changed so they can update their local version of the Permissions.

Enabling a SuperAdmin User

In most web application I have built you need one user that has access to every part of the system – I call this user SuperAdmin. And typically, I have some code that will make sure there is a SuperAdmin user in any system that the application runs. That way you can run the app with a new database and then use the SuperAdmin user to set up all the other users you need.

A logged in SuperAdmin User needs all the Permissions so that they can do anything, but that would be hard to keep updated as new permissions are added. Therefore, I added a special Permission called AccessAll and altered the UserHasThisPermission extension method to return true if the current user has the AccessAll Permission.

public static bool UserHasThisPermission(
    this Permissions[] usersPermissions, 
    Permissions permissionToCheck)
{
    return usersPermissions.Contains(permissionToCheck) 
        || usersPermissions.Contains(Permissions.AccessAll);
}

Now we have the concept of a SuperAdmin we need a way to create the SuperAdmin user. I do this via setup code in the Program class. This method makes sure that the SuperAdmin user is in the current user database, i.e. it adds a SuperUser if . Note: I am using the C# 7.1’s Main Async feature to run my startup code.

public static async Task Main(string[] args)
{
    (await BuildWebHostAsync(args)).Run();
}

public static async Task<IWebHost> BuildWebHostAsync(string[] args)
{
    var webHost = WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .Build();

    //Because I might be using in-memory databases I need to make 
    //sure they are created before my startup code tries to use them
    SetupDatabases(webHost);

    await webHost.Services.CheckAddSuperAdminAsync();
    await webHost.Services.CheckSeedDataAndUserAsync();
    return webHost;
}

The CheckAddSuperAdminAsync obtains the SuperAdmin Email and password from the appsetting.json file (see Readme file for more information).

NOTE: The SuperAdmin user is very powerful and needs to be protected. Use a long, complicated password and make sure you provide the SuperAdmin email and password by overriding the appsettings.json values on deployment. My startup code also doesn’t allow a new SuperAdmin user to be added if there is a user already in the database that has the SuperAdmin role. That stops someone adding a new SuperAdmin with their own email address once a system is live.

Other things new in version 2 of the example application

The PermissionAccessControl2 application is designed to work like a real application, using both feature and data authorization. The application pretends to be a super-simple retail sales application service for multiple companies, i.e. a multi-tenant application. I cover the multi-tenant data authorization in part 4.

The differences in version 2 of version 1 that I have not already mentioned are:

  • The code will work with either in-memory databases or SQL Server databases, i.e. it will check if users and data is already present and is so won’t try to add the user/data again
  • You can choose various application setups, such as database type and simple/complex claims setup. This is controlled by data in the appsettings.json file – see Readme file for more information on this.
  • The multi-tenant data access is hierarchical, with much more complex and robust than in version 1 – see Part 4: Building a robust and secure data authorization with EF Core for more on this.
  • There are some unit tests. Not a lot, but enough to give you an idea of what is happening.

Conclusion

The first article on my approach authorization in ASP.NET Core has been very popular, and I had great questions via my blog and at my NDC Oslo talk. This caused me to build a new version of the example app, available via a GitHub repo, with many improvements and some new articles that explains the changes/improvements.

In this article (known as Part 3) I focused on the ASP.NET Core authorization part and provided a few improvements over the version 1 application and added explanations of how to use these features.  In article Part 4 I cover data authorization with EF Core, and the Part 5 article covers the complex area of updating a user’s permissions/data key dynamically.

Hopefully the whole series, with the two example applications, will help you design and build your own authorization systems to suit your needs.

5 3 votes
Article Rating
Subscribe
Notify of
guest
26 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Sebastien
Sebastien
2 years ago

Hi Jon,
Your repository was so much help for a new developper like myself.
I am about to publish my app but as soon as I switch to production (including environment and HTTPS), I get the custom user claim from “Permission access control” mixed with the default AspNetCore authentication.
For example, I login with the customized login page (which must be using Permission Access Control 2 custom claim) and my Home Page shows logged in features (and cookies have been created in the browser). But when I click to access a feature, I get the default AspNetCore login page to log in again.

I attached the Startup.cs code regarding Authentication below. There might be something redondant which allow default AspNetCore authentication to trigger. Maybe you can tell me what to alter or remove.

Thanks in advance

Sebastien

      services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
         .AddCookie(options =>
         {
           options.Cookie.HttpOnly = true;
           options.Cookie.SecurePolicy = _environment.IsDevelopment()
            ? CookieSecurePolicy.None : CookieSecurePolicy.Always;
           options.Cookie.SameSite = SameSiteMode.Lax;
         });
       
      services.ConfigureApplicationCookie(options =>
      {
        options.AccessDeniedPath = “/Account/AccessDenied”;
        options.Cookie.Name = “AuthCookie”;
        options.LogoutPath = “/Account/Logout”;
        options.LoginPath = “/Account/Login”;
        // ReturnUrlParameter requires 
        options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
        options.SlidingExpiration = true;
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = _environment.IsDevelopment()
         ? CookieSecurePolicy.None : CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Lax;
      });

      services.Configure<CookiePolicyOptions>(options =>
      {
        options.MinimumSameSitePolicy = SameSiteMode.Strict;
        options.HttpOnly = HttpOnlyPolicy.None;
        options.Secure = _environment.IsDevelopment()
         ? CookieSecurePolicy.None : CookieSecurePolicy.Always;
      });

      services.AddDistributedMemoryCache();

      services.AddSession(options =>
      {
        options.IdleTimeout = TimeSpan.FromMinutes(60);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
      });
      #endregion
      //This registers the various databases, either as in-memory or via SQL Server (see appsetting.json for connection strings)
      var databaseSettings = new DatabaseSettings();
      _configuration.GetSection(“DatabaseSettings”).Bind(databaseSettings);
      services.RegisterDatabases(databaseSettings);

       
      #region Identity Services
       
      //https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity-configuration?view=aspnetcore-3.1
      services.AddDefaultIdentity<ItemUser>(options =>
          options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ExtraAuthorizeDbContext>()
        .AddDefaultTokenProviders()
        .AddClaimsPrincipalFactory<CustomClaimsPrincipalFactory>();

      // Register the Identity services.
      services.Configure<IdentityOptions>(options =>
      {
        // Password settings.
        options.Password.RequiredLength = 8;
        options.Password.RequireNonAlphanumeric = true;
        options.Password.RequireDigit = true;
        options.Password.RequireUppercase = true;
        options.Password.RequiredUniqueChars = 0;
        options.Password.RequireLowercase = true;
        options.Password.RequiredUniqueChars = 1;

        // Lockout settings.
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
        options.Lockout.MaxFailedAccessAttempts = 3;
        options.Lockout.AllowedForNewUsers = true;

        // User settings.
        options.User.AllowedUserNameCharacters = “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+?!&$€%”;
        options.User.RequireUniqueEmail = true;

        // Default SignIn settings.
        options.SignIn.RequireConfirmedEmail = true;
        options.SignIn.RequireConfirmedPhoneNumber = false;
      });

Vinh Nguyen
Vinh Nguyen
3 years ago

Hi Jon,

Thanks for your great article. I have a question for you that if i register my custom UserClaimsPrincipalFactory, it means that in case i implement bearer authentication with JWT format, after call SignInAsync or PasswordSignInAsync using SignInManager, the SignInManager will internally generate claims for the user. Therefore, after sign in successfully, how can i get the generated claims from SignInManager to generate JWT Token? I found a method called CreateUserPrincipaAsync. Is this the method that i need?

Thanks

Adam Stapleton
Adam Stapleton
3 years ago

Hi Jon,

Great series, keep them coming.

Have you come across any performance issues storing the permissions in claims. I’m specifically curious about the size of the cookie sent to browser if you have a lot of permissions? Is it likely to be an issue or would you need thousands of permissions before it was a problem?

Jon P Smith
3 years ago
Reply to  Adam Stapleton

Hi Adam,

Glad your are finding the series useful.

To answer your question on permissions and the size limit on cookie I did think about that and I covered it in part 1 – see https://www.thereformedprogrammer.net/a-better-way-to-handle-authorization-in-asp-net-core/#why-i-used-enums-for-the-permissions

In summary I used Enums to represent the Permissions which allowed me to use the Enum values when saving to the claims. In version 1 example code I saved them as set of hex strings, but in the version 2 example code I packed them as unicode characters. That means a user with a 1000 permissions would take up 2000 bytes, but as the cookie is encrypted it would be bigger.

For most systems I would expect a user to have a lot less than 1000 Permissions. For my client I had a more complex permissions with a hierarchy which reduced the number of permissions a user needed, but it wasn’t really needed in the end.

Adam Stapleton
Adam Stapleton
4 years ago
Reply to  Jon P Smith

Thanks again Jon 🙂

Riaan Mastenbroek
Riaan Mastenbroek
3 years ago

Fantastic read, from most of the samples I was looking for this one is on target. When will Part 5 be out?

Jon P Smith
3 years ago

Hi Riaan,

I’m glad you like the series – I’m pleased with it too, but they have taken a lot of time to build/write.

All the article were written when I had a gap in between contacts, so its difficult to say when Part 5 will be out.

Os1r1s110
Os1r1s110
3 years ago

Hi there, really nice article. I am implementing a custom authorization feature in my app and this is an interesting approach! (I also like the idea used in Squidex Headless CMS with the hierarchical permissions).

One thing that could be nice to add would be an authorization tag helper instead of using the UserHasThisPermission extension method in the views. One way to achieve this is shown in this blog post by Dave Paquette (https://www.davepaquette.com/archive/2017/11/05/authorize-tag-helper.aspx ).

Jon P Smith
3 years ago
Reply to  Os1r1s110

Hi Os1r1s110,

Glad you found the article useful. I did look at the type of hierarchical permissions you refer to, but I went with the paid-for-module feature described here https://www.thereformedprogrammer.net/a-better-way-to-handle-authorization-in-asp-net-core/#how-to-handle-optional-paid-for-features as it better met my client’s needs.

Yep, you could build a tag helper – I didn’t because my client was using a Angular.js SPA, so they read the permissions into that.

Midix
Midix
3 years ago

Thank you for the articles and the code.

I found one scenario where I could not use your approach, unfortunately.

There is an existing resource-action based authorization already in place and I had to reuse it in my web API application. The problem is that HasPermission and Permissions attribute are using claims that combine both permission and resource name in one atomic unit, but the database in my case has dynamic combinations of both.

My permission datastructure looks like this. There is a list of resource names (e.g. Document, Order) and for each of them you can assign any of CRUD permissions (and some other typical UI-only “permissions” which are not actually permissions but convenience UI element show/hide switches) from a fixed set.

So, my back-end has to somehow combine `Permissions.Edit` with the resource name in question and somehow convert it into a claim for AuthorizationAttribute to work with.

Ideally, I would like it to work like this on my base CRUD controller:

[HasPermission(Permissions.Edit, typeof(T))]
[HttpPut(“{id}”)]
public async Task>> Update(long id, T entity)

To use this with your current example when HasPermission extends Authorization attribute, I would have to somehow combine two strings to create a claim, and also I would have to correctly fill in the policy names to combine both these facts.

I guess, I’ll have to roll my own authorize attribute and process it with filters, or maybe move entirely to resource-based authorization, as described here:
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-2.2

Jon P Smith
3 years ago
Reply to  Midix

Hi Midix,

This design doesn’t support that, but here are two ideas you might find useful:

1. You could implement your [HasCrudPermission(Permissions.Edit, typeof(Order))], but underneath it maps that to Order_Crud_Edit permission (and HasBizPermissions to business logic). That way you can use Enums, which are small and fast, for the authorize and [Has???Permissions(…)] that fit the design you want.

2. For my client I had a two-level permission: first part was the item/service, e.g. Order, Email, and the second was a series of actions like Create, Update, Read, Delete, Execute, etc. This got a bit hard to correctly hand-code the Permissions Enum so I held the data in a json file and I ran some code to generate the Enum Permissions file.

On the other hand you can build your own – once I realised that the authentication could be separated from the authorization then all sorts of options opened up for me.

Javier Machin
Javier Machin
3 years ago

Hi Jon,
Thank you very much for your time writing this topic and sharing it.
I have followed your articles and implemented your security approach for a new web application (angular/net core 2.2) on the company I work for.
I’m trying that instead of calling and API endpoint to know the user’s permissions list, I read the permissions directly from the token which I stored locally, so reading the Payload I can get the permission’s claim value do a decode and get the list of permissions of the user.
The problem I’m facing is that the I get “invalid character” error when trying to decode the payload part of the token, see below snippet of the code
var tokenPayload = localStorage.getItem(‘Token’).split(‘.’)[1]; //the Payload
var payloadDecoded = window.atob(tokenPayload); //Error occurs here
“String contains an invalid character”

I see that any generated value of packedpermissionString looks like a special character “翿”.
Do you have any suggestion? Or have experienced something like this? Or just I’m trying to do something incorrectly?

Tim
Tim
3 years ago

Very interesting read, thank you. One question I have is about the SQL outputs. Monitoring with SQL profiler tells me there are about 70 SQL select statements happening for every request. This seems a lot?

Jon P Smith
3 years ago
Reply to  Tim

Hi Tim,

Fixed database now. Big refactor to make it simpler to understand and copy – see https://www.thereformedprogrammer.net/part-7-adding-the-better-asp-net-core-authorization-code-into-your-app/

Jon P Smith
3 years ago
Reply to  Tim

Thanks Tim. I found the problem!

I copied the HomeController from the original PermissionAccessControl1 project and that listed all the users on the Home page. In version 2 I changed the Index.cshtml to just show text, but forgot to take the list of all the users from the action. I’m fixed it in a new dev branch for now as I’m working on some other cleanups. I will merge to master soon.

Thanks for pointing that out.

Jon P Smith
3 years ago
Reply to  Tim

Wow, I know there is a lot on startup but shouldn’t be so many after that. I’ll have a look at that at the weekend.

Andy
Andy
3 years ago

Great stuff. Question, I don’t see the code to create the UserToRole table. I assume there is a separate table outside of the AspNet tables that would need to be created for this or no? Thanks

Andy
Andy
3 years ago
Reply to  Andy

It seems I am receiving an invalid object name ‘UserToRoles’ that is being thrown by System.Data.SqlClient.SqlException.

Jon P Smith
3 years ago
Reply to  Andy

Hi Andy,

If you have found a problem in the PermissionAccessControl2 code can you create an issue on https://github.com/JonPSmith/PermissionAccessControl2 with what you were doing and what exception you had.

Jon P Smith
3 years ago
Reply to  Andy

Hi Andy,

I have a DbContext called ExtraAuthorizeDbContext (see https://github.com/JonPSmith/PermissionAccessControl2/blob/master/DataLayer/EfCode/ExtraAuthorizeDbContext.cs ) which has the classes that I use for most of the authorization. The ASP.NET Core ApplicationDbContext (use by Identity) is only used for registering the user – all other Roles, Permissions, DataKeys are handled by my code and the ExtraAuthorizeDbContext

Gerhard Wessels
Gerhard Wessels
3 years ago

Hi Jon,

I have read your articles and they’ve proven very helpful. Our needs are very similar to those you have solved for the customer you’ve based this series on.

We are developing a Blazor application but this might be irrelevant here, but I’ll state my problem in this context regardless. In Blazor we have the AuthorizeView component that can be driven by policies or roles e.g.


@page "/counter"
@using BlazorApp1.Components





You are not authorized to view this page.



What I want to do is to resolve the policy completely dynamically. I don’t want any claims relating to the policy to be included in the ClaimsPrincipal. I thought I was onto something with my own IAuthorizationService implementation because in one of the AuthorizeAsync overloads, the one that takes the ClaimsPrincipal and policy name as arguments, I could do what I want to do. If I know who the user is and I know the policy name I can do whatever I need to do to determine if the user is authorized for that policy (leaving caching, performance etc. aside for the moment). The problem is that this particular overload is never called! The other overload is called but way too early and not when the particular AuthorizeView component comes into scope.

I have only recently started my foray into ASP.NET Core and Blazor so I cannot yet see the how AuthorizationServices, AuthorizationHandlers, AuthorizationRequirements, AuthorizationPolicyProviders, UserClaimsPrincipalFactories etc. fit together or even if these offer the tools required to solve my problem.

Do you see a way to do this?

Jon P Smith
3 years ago

Hi Gerhard,

If I understand what you want to do is use the User (most likely some form of UserId) and ‘look up’ whether that user is allowed to access something with a particular policy. Fundamentally its possible – a policy can access the user’s claims (which contains the UserId) and hence you can define whether the User can/cannot access something protected by a policy. My work was around doing this in an efficient way, i.e. minimizing the database accesses.

I certainly agree that the Auth PolicyProvider isn’t at all obvious (I found that bit hard!) – I suggest you read Jerrie Pelser’s article on the dynamically policy usage (see https://www.jerriepelser.com/blog/creating-dynamic-authorization-policies-aspnet-core/ ). I found that helpful on understand that policies.

On linking into the right place in the authorization stage: my experience is there is always a way, but its often very hard to find! As well as these articles (which use Cookie authorization) I have done it with Auth0 and Azure AD. The specific linking point is normally an event, but they have different names and signature for each approach! You just have to dig to find the right point.

Best of luck on your ASP.NET Core/Blazor adventure!

Gerhard Wessels
Gerhard Wessels
3 years ago
Reply to  Jon P Smith

I have read Jerrie’s post before but I didn’t realize I was staring the solution in the face! I re-read the article and then everything clicked into place.

Thank you very much Jon!