userManager.AddToRoleAsync() - Error: role does not exist - c#

I'm creating a user registration system using .NET Core, Identity Core, and MVC Core. I'm able to create users and create roles in the database.
Here's the form on the view that lets me select a user and select a role to add:
#using (Html.BeginForm("AddRoleToUser", "Roles"))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<p>
Username : #Html.DropDownList("UserName", (IEnumerable<SelectListItem>)ViewBag.Users, "Select ...")
Role Name: #Html.DropDownList("RoleName", (IEnumerable<SelectListItem>)ViewBag.Roles, "Select ...")
</p>
<input type="submit" value="Save" />
}
These drop-down lists are populated with users and roles that already exist in the database. They allow me to select Users , and the name of a role that I've already created. For example, I have a role with the name "admin", this form lets me select the string "admin".
Here's the action that handles adding a role to a user:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddRoleToUser(string UserName, string RoleName)
{
try
{
ApplicationUser user = _db.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
await _userManager.AddToRoleAsync(user, RoleName);
PrepopulateDropDownMenus();
ViewBag.ResultMessage = "Role created successfully!";
return View("Manage", "Roles");
}
catch (Exception ex)
{
Console.WriteLine(ex);
return View("Manage");
}
}
The action never adds the role to the user, and the exception reads "Role "ADMIN" does not exist." with no inner exception. I've tried turning the RoleName in the action parameters to all-caps, but it still does not find the role. I've also tried using the role ID instead of the name, which was also unsuccessful.
This exact code worked when I built this app using Identity 3.0 with MVC 6. It seems like something has changed in moving over to Identity Core.
Any thoughts?
Edit
Here's the code I'm using to populate the drop-down lists in RolesController via the Viewbag:
private void PrepopulateDropDownMenus()
{
var rolesList = _db.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
var usersList = _db.Users.OrderBy(u => u.UserName).ToList().Select(uu => new SelectListItem { Value = uu.UserName.ToString(), Text = uu.UserName }).ToList();
ViewBag.Roles = rolesList;
ViewBag.Users = usersList;
}
Here's how I add Identity in Startup.cs in the ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddEntityFramework()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
}
Here's the route in RolesController.cs I use to create a new role in the database:
[HttpPost]
public IActionResult Create(string rolename)
{
_db.Roles.Add(new IdentityRole()
{
Name = rolename
});
_db.SaveChanges();
ViewBag.ResultMessage = "Role created successfully!";
return RedirectToAction("Index");
}

I can't post comments yet to ask you, so, does your error say user admin does not exist, or role does not exist? I tried to duplicate your code on my end, and if the user doesn't exist you'll get a "user can't be null" error. However, if the role doesn't exist, you'll get a "Role [role] does not exist" error.
I assume that you already have the role added into your database? Here is some code I used in my seed method that essentially does what you want, minus using the views to do it:
// Add the Admin role to the database
IdentityResult roleResult;
bool adminRoleExists = await _roleManager.RoleExistsAsync("Admin");
if (!adminRoleExists)
{
_logger.LogInformation("Adding Admin role");
roleResult = await _roleManager.CreateAsync(new IdentityRole("Admin"));
}
// Select the user, and then add the admin role to the user
ApplicationUser user = await _userManager.FindByNameAsync("sysadmin");
if (!await _userManager.IsInRoleAsync(user, "Admin"))
{
_logger.LogInformation("Adding sysadmin to Admin role");
var userResult = await _userManager.AddToRoleAsync(user, "Admin");
}
EDIT
The way you're adding roles right now leaves the NormalizedName field in the Role table null, which I believe is used by the framework for adding roles to users. Try one of the following to add a role to the database instead of what you're currently doing:
var result = await _roleManager.CreateAsync(new IdentityRole(rolename));
Or this may also work (haven't tested this one though):
[HttpPost]
public IActionResult Create(string rolename)
{
_db.Roles.Add(new IdentityRole()
{
Name = rolename,
NormalizedName = rolename.ToUpper()
});
_db.SaveChanges();
ViewBag.ResultMessage = "Role created successfully!";
return RedirectToAction("Index");
}

Make sure when you are creating an AspNetRole the NormalizedName should not be null in order for the UserManager to work properly.
NormalizedName should be upper-case using .ToUpper()

Adding it straight to the database is a bad idea and violates every concept of encapsulation, and NormalizedName is not something you should be computing yourself.
See this answer
Replace this code:
var roleStore = new RoleStore<IdentityRole>(context);
await roleStore.CreateAsync(new IdentityRole(role));
with following:
var roleManager = services.GetService<RoleManager<IdentityRole>>();
await roleManager.CreateAsync(new IdentityRole(role));

This is the thing: if you are using major versions of identity (for example 6.0), the table AspNetRoles contains a Column named NormalizedName, if the string is null or differes from the role name written in capital letters you wont't be able to find the role name. You may insert manually (MMSMI) the string or delete per procedure all roles and recreate them.

Related

ASP.NET Core 6 MVC : how to configure authentication and authorisation based on person department/role

I am looking for a little advice on how to implement my security requirements within ASP.NET Core 6.
Basically I have Company A who has multiple assets (Asset A, Asset B).
I envisage the following roles:
User: can view the site in read only
Super User: can edit asset items, edit location of the asset
Administrator: can do everything a super user can but also add new users and assign them assets that they can view
My two avenues I have thought of so far are
Option 1
Change the aspnetUserRole table to include the AssetId
Override the SignInManager and change IsInRole to check whether the user has the role for the assetId. The assetId is stored in a claim.
The downfall of this is that for the role admin anyone with this would need to be added to each location which seems a bit painful.
Option 2
Policies, I am wondering if this is the better option but I am not sure how to structure it to be user friendly.
I am thinking of the policies:
Can Edit Items
Can Edit Locations
Can View Item
Can View Locations
I would then need a new table connecting the Policy, Asset and user to determine which policy they have for each asset.
However how should I handle admin who should get to do anything? Would it be as simple as a policy called SiteAdmin which is granted access everywhere?
I think you could try add another claim Asset to the user
//For SuperUser:
var userclaim = new Claim("Asset","AssetA");
//var userclaim = new Claim("Asset","AssetB");
.......
//For Administrator
var userclaim = new Claim("Asset","AllAsset");
// add the claim to User
var claimresult = await _userManager.AddClaimAsync(user, userclaim);
add roles to User:
await _rolemanager.CreateAsync(new IdentityRole("Admin"));
var addroleresult = await _userManager.AddToRoleAsync(user, "Admin");
Regist the policies:
public void ConfigureServices(IServiceCollection services)
{
.......
services.AddAuthorization(options =>
{
options.AddPolicy("AssetA",
policy => policy.RequireClaim("Asset", "AssetA", "AllAsset").RequireRole("Admin", "SuperAdmin"));
options.AddPolicy("AssetB",
policy => policy.RequireClaim("Asset", "AssetB", "AllAsset").RequireRole("Admin", "SuperAdmin"));
options.AddPolicy("SuperAdmin",
policy => policy.RequireRole("SuperAdmin"));
});
}
Tested with the controllers:
[Authorize(Policy = "AssetA")]
public IActionResult AssetA()
{
return View();
}
[Authorize(Policy = "AssetB")]
public IActionResult AssetB()
{
return View();
}
[Authorize(Policy = "SuperAdmin")]
public IActionResult SuperAdmin()
{
return View();
}
Result:

How to add a IdentityUser IdentityResult error in ASP.Net Core for a unique email address

My "User" class inherits from "IdentityUser" and I want to make the EmailAddress unique. BUT instead of creating a unique property like this
builder.Entity<User>()
.HasIndex(u => u.Email).IsUnique();
in the model builder that throws an exception when I try to register a duplicate email address in the method below, I'd like it to return a IdentityResult error, just like it does when I have a duplicate username. Some how Identity forgot to include a uniqueness for the email field!?
My register method where "result.Succeeded" is false if the username is taken/used and an IEnumerable of IdentityErrors, in "result.errors". I'd like to get the same type of error from a duplicate email. Is this possible?
[HttpPost("register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegisterDto)
{
var userToCreate = _mapper.Map<User>(userForRegisterDto);
var result = await _userManager.CreateAsync(userToCreate, userForRegisterDto.Password);
var userToReturn = _mapper.Map<UserForDetailedDto>(userToCreate);
if (result.Succeeded)
{
return CreatedAtRoute("GetUser", new { controller = "Users", id = userToCreate.Id }, userToReturn);
}
return BadRequest(result.Errors);
}
This is already supported, using RequireUniqueEmail, which defaults to false. Here’s an example of how to set it to true, taken from the docs and modified accordingly:
services.Configure<IdentityOptions>(options =>
{
options.User.RequireUniqueEmail = true;
});
You can achieve the same thing with the call to AddIdentity (or whichever variation you’re using). Here's an example of that approach:
services.AddIdentity<User, Role>(options =>
{
options.User.RequireUniqueEmail = true;
})

Why does #User.IsInRole always return false in _Layout.cshtml

I am using Razor-Pages to develop a web app. In my _Layout.cshtml file, I want to change the menu according to the role of the current user.
I, therefore, use User.IsInRole(string role) but it always returns false.
In a similar question, I read that it's somehow not possible to retrieve the user-role right after login. However, I don't understand why that would be the case.
My code:
#if (User.IsInRole(Roles.Admin.ToString())) {
<li><a asp-page="/AdminMenuPoint">Admin Menu</a>a/li>
}
My roles enum:
public enum Roles {
Supervisor, Admin
};
To summarize: Why doesn't User.IsInRole() work for my hompage (after login)?
Thanks in advance.
If you use .Net Core you need to setup:
Add Identity Service in Startup.cs
Edited
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>() // <-- Add this line
.AddEntityFrameworkStores<ApplicationDbContext>();
According to this discussion on GitHub, getting the roles and claims to show up in the cookie involves either reverting to the service.AddIdentity initialization code, or sticking with service.AddDefaultIdentity and adding this line of code to ConfigureServices:
// Add Role claims to the User object
// See: https://github.com/aspnet/Identity/issues/1813#issuecomment-420066501
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
Create Role and Assign User for Role
private async Task CreateUserRoles(IServiceProvider serviceProvider)
{
var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
var UserManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
IdentityResult roleResult;
//Adding Admin Role
var roleCheck = await RoleManager.RoleExistsAsync("Admin");
if (!roleCheck)
{
//create the roles and seed them to the database
roleResult = await RoleManager.CreateAsync(new IdentityRole("Admin"));
}
//Assign Admin role to the main User here we have given our newly registered
//login id for Admin management
ApplicationUser user = await UserManager.FindByEmailAsync("syedshanumcain#gmail.com");
var User = new ApplicationUser();
await UserManager.AddToRoleAsync(user, "Admin");
}

ASP.NET 5 MVC 6 and Entity Framework 7 issues

We can't add Owin and Entity framework 7 together. As we do so then there will be ambiguity between Microsoft.AspNet.Identity.core 2.0.0.0 and Microsoft.AspNet.Identity 3.0.0 Beta1
And hence I am not able to implement role provider in my application to manage the user roles.
After facing this issue I removed Owin references and created UserManager using
Microsoft.AspNet.Identity 3.0.0 and EF 7 but UserManager.AddToRoleAsync(user, roleName) always throws exception as below:-
InvalidOperationException: The instance of entity type 'Mozaics.DAL.Models.ApplicationUser' cannot be tracked because another
instance of this type with the same key is already being tracked. For
new entities consider using an IIdentityGenerator to generate unique
key values.
Code snippet is like this.
public async Task<ActionResult> RoleAddToUser(string UserName, string RoleName)
{
var user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var result = await UserManager.AddToRoleAsync(user, RoleName );
ViewBag.ResultMessage = "Role created successfully !";
var list = context.Roles.OrderBy(r => r.Name).ToList().Select(rr => new SelectListItem { Value = rr.Name.ToString(), Text = rr.Name }).ToList();
ViewBag.Roles = list;
return View("ManageUserRoles");
}
#sanjaypujari
Just try that one:
var user = await context.FindByNameAsync(UserName);
ApplicationUser appuser = (ApplicationUser)user;
var result = await UserManager.AddToRoleAsync(appuser, RoleName);
When executed like this, ApplicationUser shouldn't be tracked twice.
Same thing helps, too, if you want to update a ApplicationUser.
I run to this issue. One solution that worked for me was that getting the user via the UserManager and then adding the role:
instead of
var user = context.Users.Where(u => u.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
var result = await UserManager.AddToRoleAsync(user, RoleName );
use the following
var user = await UserManager.FindByNameAsync(UserName);
var result = await UserManager.AddToRoleAsync(user, RoleName );

How to auto-generate objects in the Register Model C# ASP.NET MVC 4?

I using the Internet Application template in C# MVC 4 which generates the Account model and controller for you in Visual Studio. This gives me basic form functionality for logging in. I modified the Register class in the Account model to also take a FirstName, LastName, and Email in addition to the username and password. My table with user information is Users. When a user submits their information WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
is ran and it adds the Username to my Users table. So at that point I have my PK Id and Username in the table. My User class definition has a custom object Portfolio. What I am trying to do is when the user registers they get added to the table and then I find that User and set the values for the rest of the data in the table and instantiate the Portfolio object. This would complete my register process.Currently, it adds the user to the table with just the Username adds them to the role and redirects back to index. My var user = _db.Users.Find(userId) is apparently not finding anything for some reason. I even tried to cheat and hardcode the userId because I knew what the next Id would be in the Users table and it still did not modify the table values. FirstName, LastName, Email all remain null in the table and no object is instantiated for Portfolio. I have my context so it should work. I did something similar in my Seed method and it worked fine. I am not sure why it is not working here. Does anyone have suggestions? Thank you.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(RegisterModel model, TeamProject.Models.ProjectDb2 context)
{
if (ModelState.IsValid)
{
// Attempt to register the user
try
{
WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
var userId = _db.Users
.Where(u => u.UserName == model.UserName)
.Select(u => u.Id).First();
var user = _db.Users.Find(userId);
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Email = model.Email;
user.Portfolio = new Portfolio
{
Stocks = new List<Stock>
{
}
};
context.Users.AddOrUpdate(user);
System.Web.Security.Roles.AddUserToRole(model.UserName, "Users");
WebSecurity.Login(model.UserName, model.Password);
return RedirectToAction("Index", "Home");
}
As per the above comment:
You do not appear to be calling context.SaveChanges anywhere.

Categories