Search code examples
c#sql-serverasp.net-mvcasp.net-coreasp.net-identity

ASP.NET Core Identity Role: Access error even though the role is defined


In my project, the user area is kept on the root side and admin is in areas. The login page, layouts and views of both areas are different from each other. How will I do the role setting in these two pages. I mean like this,

There are multiple users on the admin side, like admin A can't access page-1, admin B can't access page-2... User side is the same way.

Admin and user side should have separate AccessDeniedPath paths, and if not logged in, LoginPath should have separate paths.

Example:
Admin: Management/Login/Index
User: Login/Index

I should also check this when I log in. If the user is logged in by admin, they should get a "You are not authorized" warning.

This is the code I tried,

With this code, I can control while logging in, but I cannot make controller based role.

Program.cs,

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<Context>();
builder.Services.AddIdentity<AppUser, AppRole>(options =>
{
    options.User.RequireUniqueEmail = true;
}).AddEntityFrameworkStores<Context>().AddErrorDescriber<CustomIdentityValidator>().AddDefaultTokenProviders().AddRoles<AppRole>();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie("UserLogin", options =>
{
    options.LoginPath = "/Login/Index";
    options.Cookie.Name = "UserLoginCookie";
    options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
    options.Events = new CookieAuthenticationEvents
    {
        OnValidatePrincipal = context =>
        {
            var now = DateTime.UtcNow;
            var expires = context.Properties.ExpiresUtc;

            if (expires != null && expires.Value < now)
            {
                context.RejectPrincipal();
                context.ShouldRenew = true;
                context.Response.Redirect("/Login/Index");
            }
            return Task.CompletedTask;
        }
    };
})
.AddCookie("ManagementLogin", options =>
{
    options.LoginPath = "/Management/Login/Index";
    options.Cookie.Name = "ManagementLoginCookie";
    options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
    options.Events = new CookieAuthenticationEvents
    {
        OnValidatePrincipal = context =>
        {
            var now = DateTime.UtcNow;
            var expires = context.Properties.ExpiresUtc;

            if (expires != null && expires.Value < now)
            {
                context.RejectPrincipal();
                context.ShouldRenew = true;
                context.Response.Redirect("/Management/Login/Index");
            }
            return Task.CompletedTask;
        }
    };
});

builder.Services.AddSession();
builder.Services.AddDistributedMemoryCache();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseSession();

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.MapControllerRoute(
    name: "areas",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

app.Run();

LoginController.cs,

[AllowAnonymous]
public class LoginController : Controller
{
    private readonly SignInManager<AppUser> _signInManager;
    private readonly UserManager<AppUser> _userManager;

    public LoginController(SignInManager<AppUser> signInManager, UserManager<AppUser> userManager)
    {
        _signInManager = signInManager;
        _userManager = userManager;
    }

    [HttpGet]
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Index(LoginDto loginDto)
    {
        LoginValidator validationRules = new LoginValidator();
        ValidationResult validationResult = await validationRules.ValidateAsync(loginDto);
        if (validationResult.IsValid)
        {
            var user = await _userManager.FindByNameAsync(loginDto.UserName);

            if (user != null)
            {
                var result = await _signInManager.CheckPasswordSignInAsync(user, loginDto.Password, true);

                if (result.Succeeded)
                {
                    if (!await _userManager.IsEmailConfirmedAsync(user))
                    {
                        TempData["Mail"] = "Mail";
                        return RedirectToAction("Confirm", "Confirmation");
                    }
                    else
                    {
                        var login = await _signInManager.PasswordSignInAsync(loginDto.UserName, loginDto.Password, true, true);

                        if (login.Succeeded)
                        {
                            if (await _userManager.IsInRoleAsync(user, UserRoles.Kullanici))
                            {
                                var claims = new List<Claim>
                                {
                                    new Claim(ClaimTypes.Name, user.UserName),
                                };

                                var userIdentity = new ClaimsIdentity(claims, "UserLogin");
                                var userPrincipal = new ClaimsPrincipal(userIdentity);

                                await HttpContext.SignInAsync("UserLogin", userPrincipal);

                                return RedirectToAction("Index", "Home");
                            }
                            else
                            {
                                ModelState.AddModelError("", "Bu sayfaya erişim izniniz bulunmamaktadır.");
                                return View();
                            }
                        }
                        else if (login.IsLockedOut)
                        {
                            ModelState.AddModelError("", "Fazla sayıda hatalı giriş yaptığınız için hesabınız kilitlendi. Lütfen daha sonra tekrar deneyiniz. Şifrenizi hatırlamıyorsanız 'Şifremi Unuttum' kısmından yeni bir şifre belirleyebilirsiniz.");
                        }
                        else
                        {
                            ModelState.AddModelError("", "Hatalı Kullanıcı Adı veya Şifre");
                        }
                    }
                }
                else if (result.IsLockedOut)
                {
                    ModelState.AddModelError("", "Fazla sayıda hatalı giriş yaptığınız için hesabınız kilitlendi. Lütfen daha sonra tekrar deneyiniz. Şifrenizi hatırlamıyorsanız 'Şifremi Unuttum' kısmından yeni bir şifre belirleyebilirsiniz.");
                }
                else
                {
                    ModelState.AddModelError("", "Hatalı Kullanıcı Adı veya Şifre");
                }
            }
            else
            {
                ModelState.AddModelError("", "Böyle Bir Hesap Bulunamadı");
            }
        }
        else
        {
            foreach (var item in validationResult.Errors)
            {
                ModelState.AddModelError(item.PropertyName, item.ErrorMessage);
            }
        }
        return View();
    }

    [HttpGet]
    public IActionResult ForgotPassword()
    {
        return View();
    }
}

HomeController.cs,

[Authorize(AuthenticationSchemes = "UserLogin")]
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

HomeController.cs(edited),

**[Authorize(AuthenticationSchemes = "UserLogin", Roles="User-IT")]**
public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

If I use it like this, I get an access denied warning even though I have User-IT authorization.


Solution

  • When you use the [Authorize] attribute with both AuthenticationSchemes and Roles specified, the authorization logic requires satisfying both conditions. In other words, a user must be authenticated using the specified authentication scheme (UserLogin) and must also belong to the specified role (User-IT) to access the action.

    Then when you use cookie authentication, you need add the role to the claims and login the user by HttpContext.SignInAsync:

    var claims = new List<Claim>
    {
    
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.Role, "User-IT")   //add this line...
    };
    
    var userIdentity = new ClaimsIdentity(claims, "UserLogin");
    var userPrincipal = new ClaimsPrincipal(userIdentity);
    
    await HttpContext.SignInAsync("UserLogin", userPrincipal);
    

    Declare with the authorize attribute like below:

    [Authorize(AuthenticationSchemes = "UserLogin", Roles = "User-IT")]
    

    Reference:

    https://stackoverflow.com/a/64766150/11398810

    You may mix cookie authentication with Identity. For how to use Identity authentication with roles, you can leverage the built-in [Authorize] attribute along with the Roles parameter([Authorize( Roles = "User-IT")]) and login by the following code:

    var login = await _signInManager.PasswordSignInAsync(loginDto.UserName, loginDto.Password, true, true);