Search code examples
asp.netasp.net-mvcasp.net-coreasp.net-identityasp.net-core-3.1

ASP.NET Core 3.1 MVC Access Denied role based authorization - Conflict with custom UserClaimsPrincipalFactory


I have read a lot of answers on stackoverflow for similar problems but still cannot figure out what I am doing wrong.

My AspNetUsers, AspNetRoles and AspNetUserRoles tables have been seeded correctly.

This is my ConfigureServices:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseLazyLoadingProxies().UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = false)
            .AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();
        services.AddControllersWithViews();
        services.AddMvc();
        services.AddRazorPages();
        services.AddAuthorization(options => {
            options.AddPolicy("ManagerOnly", policy => policy.RequireRole("Manager"));
            options.FallbackPolicy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
        });
        services.Configure<IdentityOptions>(options =>
        {
            ...
        });

        services.ConfigureApplicationCookie(options =>
        {
            ...
        });

        services.Configure<PasswordHasherOptions>(option =>
        {
            ...
        });
    }

I am using the default identity management that Visual Studio scaffolds when we select "individual user accounts" as authentication method.

When I put [Authorize(Policy = "ManagerOnly"] on any Action, I get the Access Denied result even when logged in from the correct user account which is in the Manager role.

EDIT:

I have verified that the problem disappears when I remove .AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();

Following is my CustomUserClaimsPrincipalFactory:

public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
    public CustomUserClaimsPrincipalFactory(
    UserManager<ApplicationUser> userManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, optionsAccessor)
    {
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);
        identity.AddClaim(new Claim("FullName", user.FullName ?? "Unnamed"));
        return identity;
    }
}

Any idea why this would conflict with Roles?

EDIT 2:

Solution for noobs like me:

public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    public CustomUserClaimsPrincipalFactory(
    UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
    {
    }

Per my understanding, role manager needs to be injected in the custom claims factory so that roles defined in the database can be loaded when we override the base claims generation.


Solution

  • I think your dependencies that you inject to CustomUserClaimsPrincipalFactory are somehow different or not resolved properly thus not connecting to the DB.

    Please make sure the role manager is properly setup in your startup.