Search code examples
c#asp.net-coreasp.net-core-mvcasp.net-core-identity

AspNetCore 3.1 Role Policies not working on views controller


I've implemented Identity Scaffold on my project and Authentication works like a charm, I watched some tutorials of how to implement Roles with Identity and I'm having a hard time with the Role Policies labels on the controllers.

They all tell me I'm not authorized.

This is my startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public IConfiguration Configuration { get; }
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseMySql(
                Configuration.GetConnectionString("DefaultConnection")));
        services.AddIdentity<IdentityUser, IdentityRole>(options => 
            options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
        services.AddIdentityCore<ApplicationUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultUI();           
        services.AddAuthorization(options =>
        {
            options.AddPolicy("AdminAccess", policy => policy.RequireRole("Admin"));

            options.AddPolicy("ManagerAccess", policy =>
                policy.RequireAssertion(context =>
                            context.User.IsInRole("Admin")
                            || context.User.IsInRole("Manager")));

            options.AddPolicy("UserAccess", policy =>
                policy.RequireAssertion(context =>
                            context.User.IsInRole("Admin")
                            || context.User.IsInRole("Manager")
                            || context.User.IsInRole("User")));
        });

        services.AddTransient<IEmailSender, EmailSender>(i =>
            new EmailSender(
                Configuration["EmailSender:Host"],
                Configuration.GetValue<int>("EmailSender:Port"),
                Configuration.GetValue<bool>("EmailSender:EnableSSL"),
                Configuration["EmailSender:UserName"],
                Configuration["EmialSender:Password"]
            )
        );             
        services.AddControllers().AddNewtonsoftJson(options =>
            options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
        );
        services.AddControllersWithViews();
        services.AddRazorPages().AddRazorRuntimeCompilation();
    }
    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();           
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Charts}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });
    }
}

I have a PolicyController.cs

public class PolicyController : Controller
{
    public IActionResult Index() => View();

    [Authorize(Policy = "UserAccess")]
    public IActionResult UserPage() => View();

    [Authorize(Policy = "ManagerAccess")]
    public IActionResult ManagerPage() => View();

    //[Authorize(Policy = "AdminAccess")]
    public IActionResult AdminPage()
    {
        // This returns FALSE
        if (User.IsInRole("Admin"))
            ViewBag.Message = "You Admin";
        ViewBag.Message = "No Admin";
        return View();
    }
}

I also have a view where I create roles and link them to a user, I even lookup at my DB and I see the RoleID with the UserID created on the aspnet-user-roles table but I'm not able to get on those test views with my created roles which are exactly typed as in the startup.cs

[HttpPost]
public async Task<IActionResult> UpdateUserRole(UpdateUserRoleViewModel vm)
{
    var user = await _userManager.FindByEmailAsync(vm.UserEmail);

    if (vm.Delete)
        await _userManager.RemoveFromRoleAsync(user, vm.Role);
    else
        await _userManager.AddToRoleAsync(user, vm.Role);

    return RedirectToAction("Index");
}

What am I doing wrong?


Solution

  •   services.AddIdentity<IdentityUser, IdentityRole>(options => 
            options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
        services.AddIdentityCore<ApplicationUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultUI();
    

    Perhaps the issue is related to the above code, from the code, it seems that you want to add custom user data to the AspNetUsers table. So, you will create a ApplicationUser.cs class and inheriting from IdentityUser, code like this:

    public class ApplicationUser: IdentityUser
    {
        //custom user data.
        public string CustomTag { get; set; }
    }
    

    Then, in the Startup.ConfigureServices, we could replace IdentityUser with ApplicationUser, and use the following code to configure the Identity:

            services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
                     .AddEntityFrameworkStores<ApplicationDbContext>()
                     .AddDefaultTokenProviders()
                     .AddDefaultUI();
    
            //there is no need to add the following code:
            //services.AddIdentityCore<ApplicationUser>()
            //    .AddEntityFrameworkStores<ApplicationDbContext>()
            //    .AddDefaultUI();
    

    [Note] By using the above code, for all of the Scaffolding Identity razor pages, might be you also have to replace the IdentityUser with ApplicationUser.

    If you don't want to add custom user data to the AspNetUsers table via the ApplicationUser class, try to remove the following code:

            //services.AddIdentityCore<ApplicationUser>()
            //    .AddEntityFrameworkStores<ApplicationDbContext>()
            //    .AddDefaultUI();
    

    Besides, if still not working, please recheck the database, whether you are using the correct database, and check the user's roles from the AspNetUsers, AspNetRoles and AspNetUserRoles table.