简体   繁体   中英

User.IsInRole returns nothing in ASP.NET Core (Repository Pattern implemented)

I have an ASP.NET Core (Full .NET Framework) application with the following configuration:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>(p => {
        p.Password.RequireDigit = true;
        p.Password.RequireNonAlphanumeric = false;
        p.Password.RequireUppercase = true;
        p.Password.RequiredLength = 5;
    })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
    services.AddTransient<IDbFactory, DbFactory>();
    services.AddTransient<IUnitOfWork, UnitOfWork>();

    services.AddTransient<IUserRepository, UserRepository>();
    services.AddTransient<IUserService, UserService>();
}

The ApplicationUser extends from IdentityUser and ApplicationDbContext extends IdentityDbContext

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base()
    {
    }

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    public virtual void Commit()
    {
        base.SaveChanges();
    }

    protected override void OnConfiguring(DbContextOptionsBuilder builder)
    {
        base.OnConfiguring(builder);

        builder.UseSqlServer("connection string here");
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        // Customize the ASP.NET Identity model and override the defaults if needed.
        // For example, you can rename the ASP.NET Identity table names and more.
        // Add your customizations after calling base.OnModelCreating(builder);

        // Configure model
        // Identity
        new Configuration.Identity.ApplicationUserConfiguration(builder.Entity<ApplicationUser>());
        new Configuration.Identity.ApplicationUserProfileConfiguration(builder.Entity<ApplicationUserProfile>());
        new Configuration.Identity.RoleConfiguration(builder.Entity<IdentityRole>());
        new Configuration.Identity.RoleClaimConfiguration(builder.Entity<IdentityRoleClaim<string>>());
        new Configuration.Identity.ApplicationUserRoleConfiguration(builder.Entity<IdentityUserRole<string>>());
        new Configuration.Identity.ApplicationUserClaimConfiguration(builder.Entity<IdentityUserClaim<string>>());
        new Configuration.Identity.ApplicationUserLoginConfiguration(builder.Entity<IdentityUserLogin<string>>());
        new Configuration.Identity.ApplicationUserTokenConfiguration(builder.Entity<IdentityUserToken<string>>());
    }
}

Here is my demo data:

Role table

角色表

User table

用户表

UserRole table

用户角色表

In my Login Action i have the following:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            if (User.IsInRole("Admin"))
            {
                return RedirectToAction("Index", "Home", new { area = "Admin" });
            }
            return RedirectToAction("Index", "Home");
        }
        if (result.RequiresTwoFactor)
        {
            return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning(2, "User account locked out.");
            return View("Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

What i want to achieve is to redirect the user to a certain area after login.

The current problem i'm facing is that the function User.IsInRole("Admin") returns false and in debug mode, if i look at the usermanager, the current user doesn't have the roles loaded (Count = 0).

Any thoughts would be appreciated.

Update 1

Ignore the Role Id cause is wrong. In fact the user is mapped with the correct value.

User.IsInRole is checking the cookie. But you are checking this within the same http request as you sign-in. Cookie is simply not there yet - it will be available on the reply or next request.

At that point you need to use ApplicationUserManager.IsInRoleAsync(TKey userId, string role) to check against the database.

If anyone (as me) is struggling with this in .Net Core 2.1, this link may help .

In short, if you are using AddDefaultIdentity like this:

services.AddDefaultIdentity<ApplicationUser>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

Then Roles won't work as they are not implemented in DefaultIdentity.

What worked for me is replacing it with:

services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddRoleManager<RoleManager<IdentityRole>>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultUI()
            .AddDefaultTokenProviders();

Also, if you signed in before above fix, logout and login again, so identity claims are refreshed. Now it should work.

After hours of searching I realized this work with ASP.Net Core when using Azure Active Directory and Roles

  User.HasClaim(ClaimTypes.Role,"admin");

This Doesn't

  User.IsInRole("admin");

Starting in .Net Core 2.1 (and also works in 3.1), AddDefaultIdentity is the same as calling:

  • AddIdentity
  • AddDefaultUI
  • AddDefaultTokenProviders

To add role functionality, go to Startup.cs under ConfigureServices you can use .AddRoles like so:

services.AddDefaultIdentity<IdentityUser>()
    .AddRoles<IdentityRole>()            //<-- This line
    .AddEntityFrameworkStores<ApplicationDbContext>();

That's all that is needed. It is crucial to logout and login again as someone mentioned above.

For the record (and just to test), I tried services.AddIdentity :

IServiceCollection does not contain a defintion for 'AddIdentity'...

and services.AddIdentityCore (no error until Debug and displaying the page):

InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action configureOptions).

There may be more you can do to get the latter two working, but the code I posted for AddDefaultIdentity is all I needed in order to get User.IsInRole and other role functionality working in .NET Core 2.1 and up to 3.1 thus far.

I also found the same issue as Kaptain Babbalas and discovered that re-adding the roles manually within OnTokenValidated doubles up the result from User.Claims but causes User.IsInRole to function

options.Events = new OpenIdConnectEvents
{
    OnTokenValidated = (context) =>
    {
        var claims = new List<Claim>();
        foreach (var claim in context.Principal.Claims)
        {
            if (claim.Type == ClaimTypes.Role) claims.Add(new Claim(ClaimTypes.Role, claim.Value));
        }

        var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
        context.Principal.AddIdentity(claimsIdentity);

        return Task.CompletedTask;
    }
};

User.IsInRole() works on the next Request after SignIn . In your code, SignIn and User.IsInRole() are executing in the same Request . So to apply a manual redirect, you can put your authentication code in another action and redirect to that action from your Login() action as following:

public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
    var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, lockoutOnFailure: false);
    if (result.Succeeded)
    {
        return RedirectToAction("ObeyMyOrder");
    }
}

public async Task<IActionResult> ObeyMyOrder()
{
        if (User.IsInRole("Admin"))
        {
            return RedirectToAction("Index", "Home", new { area = "Admin" });
        }
        return RedirectToAction("Index", "Home");
}

Now the User.IsInRole() will work.

就我而言,当用户已经登录时,我已将用户添加到数据库中的角色。注销并再次登录解决了该问题。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM