Create User Role in mvc 4 - asp.net-mvc-4

Thanks in advance
I need to set Role in particular users depends on their Role. I had
try to goggled many websites but I did not get clear idea about this
Roles. I need to implement this Role concept in my mvc project.
Controller:-
[AllowAnonymous]
public ActionResult EditableUserDetails( )
{
if (!Roles.RoleExists("test"))
Roles.CreateRole("test");
var UserName = Session["UserName"].ToString();
Roles.AddUserToRole(UserName, "test");
var linq = (from db in EntityObj.Users
where db.IsActive == true
select new EditableUserDetails
{
UserId = db.UserId,
UserName = db.UserName,
Password = db.Password,
Category = db.Category
}).ToList();
var data = linq.ToList();
return View(data);
}
If I run this code I got this following error:-

Related

User getting created before custom user validator runs in Identity core

I am using identity core for user management in .net core 3.1 web api. Now, I want to check the users email for something and if it meets the requirement only then he will be created. The code below tells a lot about what I want to achieve
I have a custom user validator as below:
public class CustomEmailValidator<TUser> : IUserValidator<TUser>
where TUser : User
{
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
TUser user)
{
User userFromEmail = null;
if(!string.IsNullOrEmpty(user.Email))
userFromEmail = manager.FindByEmailAsync(user.Email).Result;
if (userFromEmail == null)
return Task.FromResult(IdentityResult.Success);
return Task.FromResult(
IdentityResult.Failed(new IdentityError
{
Code = "Err",
Description = "You are already registered with us."
}));
}
}
I add the validator in my startup as below:
services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
{
opt.User.RequireUniqueEmail = false;
opt.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz0123456789._-";
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 6;
opt.Password.RequireNonAlphanumeric = true;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
})
.AddUserValidator<CustomEmailValidator<User>>();
builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
builder.AddEntityFrameworkStores<DataContext>();
builder.AddRoleValidator<RoleValidator<Role>>();
builder.AddRoleManager<RoleManager<Role>>();
builder.AddSignInManager<SignInManager<User>>();
As can be seen, I want to use the default user validation and my custom validation too. The problem being the user gets created right after the default validation and the email always turns out as exists in my custom validation. I don't really want to override my default validations.
Creating the user as below:
[HttpPost("Register")]
public async Task<IActionResult> Register(UserForRegisterDto userForRegister)
{
var userToCreate = _mapper.Map<User>(userForRegister);
var result = await _userManager.CreateAsync(userToCreate, userForRegister.Password);
if (result.Succeeded)
{
var roleresult = await _userManager.AddToRoleAsync(userToCreate, "Member");
return Ok(roleresult);
}
return BadRequest(result.Errors);
}
Note This is not my actual use case. I know I can check for unique email in my default validation by making opt.User.RequireUniqueEmail = true. This is just to clear a concept for further development.
Update After further debugging, I see that the custom validation method is called twice. Once before user creation and once after creation for some reason. I insert a new unique email and the custom validation passes success and after user creation, custom validation is called again and find the email registered already and throws an error message. This is weird
Found out that AddToRoleAsync was calling the custom validator again and was finding the user present in the table. Had to include a check whether the user found in the table with the same email is the same as user getting getting updated.
Code below:
public class CustomEmailValidator<TUser> : IUserValidator<TUser>
where TUser : User
{
public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
TUser user)
{
User userFromEmail = null;
if(!string.IsNullOrEmpty(user.Email))
userFromEmail = manager.FindByEmailAsync(user.Email).Result;
if (userFromEmail == null)
return Task.FromResult(IdentityResult.Success);
else {
if(string.Equals(userFromEmail.Id, user.Id))
{
return Task.FromResult(IdentityResult.Success);
}
}
return Task.FromResult(
IdentityResult.Failed(new IdentityError
{
Code = "Err",
Description = "You are already registered with us."
}));
}
}
This should help a lot of people

How to get Role Claims?

I've created a user and attached to him a role that has a number of claims. The problem is I don't see a direct way to access retrieve them using Entity Framework Core and Identity integration. Here's what I'd like to do ideally:
return _context.Users
.Include(u => u.Roles)
.ThenInclude(r => r.Role)
.ThenInclude(r => r.Claims)
But there's not Role property, just RoleId. So I can not Include role claims. Of course I get make a separate query to get claims or even use RoleManager:
var user = _context.Users.Single(x => x.Id == ...);
var role = _roleManager.Roles.Single(x => x.Id == user.Roles.ElementAt(0).RoleId);
var claims = _roleManager.GetClaimsAsync(role).Result;
but it looks inefficient and even ugly. There should be a way to make a single query.
My last hope was Controller.User property (ClaimsIdentity). I hoped it somehow smartly aggregates claims from all the roles. But seems like it doesn't...
You can use SQL-like query expressions and get all claims from all roles of a user like this:
var claims = from ur in _context.UserRoles
where ur.UserId == "user_id"
join r in _context.Roles on ur.RoleId equals r.Id
join rc in _context.RoleClaims on r.Id equals rc.RoleId
select rc;
You can add navigation properties.
public class Role : IdentityRole
{
public virtual ICollection<RoleClaim> RoleClaims { get; set; }
}
public class RoleClaim : IdentityRoleClaim<string>
{
public virtual Role Role { get; set; }
}
Then you have to configure your identity db context:
public class MyIdentityDbContext : IdentityDbContext<User, Role, string, IdentityUserClaim<string>, IdentityUserRole<string>, IdentityUserLogin<string>, RoleClaim, IdentityUserToken<string>>
Usage:
await _context.Roles.Include(r => r.RoleClaims).ToListAsync();
At the end it generates the following query:
SELECT `r`.`Id`, `r`.`ConcurrencyStamp`, `r`.`Name`, `r`.`NormalizedName`, `r0`.`Id`, `r0`.`ClaimType`, `r0`.`ClaimValue`, `r0`.`RoleId`
FROM `roles` AS `r`
LEFT JOIN `role_claims` AS `r0` ON `r`.`Id` = `r0`.`RoleId`
ORDER BY `r`.`Id`, `r0`.`Id`
Source: Identity model customization in ASP.NET Core
Make sure you are adding the roles and claims correctly. Below is an example of how I create a user and add claims and roles.
private async Task<IdentityResult> CreateNewUser(ApplicationUser user, string password = null){
//_roleManger is of type RoleManager<IdentityRole>
// _userManger is of type UserManager<ApplicationUser>
//and both are injected in to the controller.
if (!await _roleManger.RoleExistsAsync("SomeRole")){
await _roleManger.CreateAsync(new IdentityRole("SomeRole"));
}
var result = password != null ? await _userManager.CreateAsync(user, password) : await _userManager.CreateAsync(user);
if(result.Succeeded) {
await _userManager.AddToRoleAsync(user, "SomeRole");
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Name, user.Email));
}
return result;
}
Then you can use the _userManager to get the claims. This is how I get the current user using _userManager. Then you can just call something like this:
var claims = await _userManager.GetClaimsAsync(user);

ASP.NET Core Identity Role, Claim and User

I am an ASP.NET Core beginner. I'm stuck in role, claim and user relationship.
I have a user Ben, user belongs to Admin role. Admin role has claims view-page and edit-page in database.
But I can't get claims and roles to be belonging to that user:
(Please see comment in code)
var user = await _userManager.FindByNameAsync(applicationUser.UserName);
if(user != null) {
var userClaims = await _userManager.GetClaimsAsync(user); // empty, WHY ?
var userRoles = await _userManager.GetRolesAsync(user); // ['admin']
var adminRole = DbContext.Roles.FirstOrDefault(x => x.Name == "Admin");
IList<Claim> adminClaims;
if(adminRole != null)
{
adminClaims = await _roleManager.GetClaimsAsync(adminRole);
// correct => ['view-page', 'edit-page']
}
}
}
In my mind, I understand when a user is a member of a role, he inherit that role's claims.
Default ASP.NET Identity have 5 tables:
Users.
Roles.
UserRoles - A user can have many roles.
RoleClaims - A role can have many claims.
UserClaims - A user can have many claims.
Do i think correct ? Why userManager.GetClaimsAsync(user) returns empty claims ?
Any suggestion?
Why userManager.GetClaimsAsync(user) returns empty claims ?
Because UserManager.GetClaimsAsync(user) queries the UserClaims table. Same for
RoleManager.GetClaimsAsync(role) queries the RoleClaims table.
But by design in ASP.NET Identity Core when a user is a member of a role, they automatically inherit the role's claims. You can check the ClaimsPrincipal, for example inside a controller action:
var claims = User.Claims.ToList();
You can see the code in UserClaimsPrincipalFactory.cs that creates a ClaimsPrincipal from an user.
I have had to deal with this issue recently and to solve the problem of locating Users by a particular Claim that came from a Role is to create a new Claim object with the values from the Role Claim:
var role = await roleManager.FindByNameAsync(yourRoleName);
if(role != null)
{
var roleClaims = await roleManager.GetClaimsAsync(role);
if(roleClaims != null && roleClaims.Count() > 0)
{
foreach(var claim in roleClaims.ToList())
{
var users = await userManager.GetUsersForClaimAsync(new Claim(claim.Type, claim.Value));
if(users != null && users.Count() > 0)
{
foreach(var user in users.ToList())
{
//This is an example of only removing a claim, but this is the
//area where you could remove/add the updated claim
await userManager.RemoveClaimAsync(user, new Claim(claim.Type, claim.Value));
}
}
}
}
}
This allowed me to Update/Delete a role with claims and pass those changes to the Users to be Re-Issued/Removed that were assigned the roles and claims. However, I am still looking for something more elegant/easier with less code.

Using Roles with Forms Authentication

I'm using forms authentication in my MVC application. This is working fine. But not I want to adjust authorization to only allow people in certain roles. The logins correspond to users in active directory and the roles correspond to the groups the users are in.
For authentication, I simply call FormsAuthentication.SetAuthCookie(username, true) after verifying the login.
For authorizing, I first applied the attribute to the controllers I want to secure
[Authorize(Roles = "AllowedUsers")]
public class MyController
...
Next, I'm handling the OnAuthenticate event in global.asax.
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
// Create WindowsPrincipal from username. This adds active directory
// group memberships as roles to the user.
args.User = new WindowsPrincipal(new WindowsIdentity(ticket.Name));
FormsAuthentication.SetAuthCookie(ticket.Name, true);
}
catch (Exception e)
{
// Decrypt method failed.
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " + "supported for this application.");
}
}
With this when someone accesses the website they get the login screen. From there they can actually log in. However, somehow it doesn't save the auth cookie and they get a login screen after the next link they click. I tried adding a call to SetAuthCookie() in OnAuthenticate() but they made no difference.
Before I added this event handler to handle authorization, authentication worked fine. So somewhere in the framework User is being set. I'm wondering if this the correct approach and I'm just missing something or if I need a different approach.
What do I need to do to get this to work?
Thanks,
Scott
It seems like my initial approach won't work. I was trying to get ASP.NET to automatically load user roles from their AD account. No comment was given on whether this was possible. However, the research I've done indicates I'll have to write code to load AD group memberships into user roles.
The solution to creating the user principal that ASP.NET MVC uses appears to be to create it in FormsAuthentication_OnAuthenticate() and assign it to Context.User. It appears if I don't set Context.User ASP.NET MVC creates a user principal based off the auth ticket after FormsAuthentication_OnAuthenticate() returns. Additionally, ASP.NET MVC appears to do nothing with Context.User if I set it in FormsAuthentication_OnAuthenticate().
The following is what I ended up doing.
This is the code that handles authentication
public ActionResult LogOn(FormCollection collection, string returnUrl)
{
// Code that authenticates user against active directory
if (authenticated)
{
var authTicket = new FormsAuthenticationTicket(username, true, 20);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = DateTime.Now.AddMinutes(30);
Response.Cookies.Add(authCookie);
if (Url.IsLocalUrl(returnUrl)
&& returnUrl.Length > 1
&& returnUrl.StartsWith("/", StringComparison.OrdinalIgnoreCase)
&& !returnUrl.StartsWith("//", StringComparison.OrdinalIgnoreCase)
&& !returnUrl.StartsWith("/\\", StringComparison.OrdinalIgnoreCase))
{
return Redirect(returnUrl);
}
else
{
return Redirect("~/");
}
}
return View();
}
I initially tried just calling FormsAuthentication.SetAuthCookie(username, true) instead of manually creating, encrypting, and adding it to the Response cookie collections. That worked in the development environment. However, it didn't after I published to the website.
This is the log off code
public ActionResult LogOff()
{
var authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
authCookie.Expires = DateTime.Today.AddDays(-1);
}
Response.Cookies.Add(authCookie);
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Home");
}
FormsAuthentication.SignOut() doesn't seem to do anything after I switched to manually creating, encrypting, and adding the auth ticket to the response cookie collection in the logon code. So I had to manually expire the cookie.
This is the code I have for FormsAuthentication_OnAuthenticate()
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || string.IsNullOrWhiteSpace(authCookie.Value))
return;
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
UserData userData = null;
if (Application["UserData_" + authTicket.Name] == null)
{
userData = new UserData(authTicket.Name);
Application["UserData_" + authTicket.Name] = userData;
}
else
{
userData = (UserData)Application["UserData_" + authTicket.Name];
}
Context.User = new GenericPrincipal(new GenericIdentity(authTicket.Name), userData.Roles);
}
UserData is a class I created to handle caching user roles. This was needed because of the time it takes for active directory to return the group memberships the user belongs to. For completeness, the following is the code I have for UserData.
public class UserData
{
private int _TimeoutInMinutes;
private string[] _Roles = null;
public string UserName { get; private set; }
public DateTime Expires { get; private set; }
public bool Expired { get { return Expires < DateTime.Now; } }
public string[] Roles
{
get
{
if (Expired || _Roles == null)
{
_Roles = GetADContainingGroups(UserName).ToArray();
Expires = DateTime.Now.AddMinutes(_TimeoutInMinutes);
}
return _Roles;
}
}
public UserData(string userName, int timeoutInMinutes = 20)
{
UserName = userName;
_TimeoutInMinutes = timeoutInMinutes;
}
}
Roles can also be stored in a cookie and you have at least two options:
a role provider cookie (another cookie that supports the forms cookie), set with cacheRolesInCookie="true" on a role provider config in web.config. Roles are read the first time authorization module asks for roles and the cookie is issued then
a custom role provider that stores roles in the userdata section of the forms cookie, roles have to be added to the user data section of the forms cookie manually
The Authorization module asks the current principal for user roles, which, if role provider is enabled, either scans the role cookie (the first option) or fires the custom role provider methods.
Yet another, recommended approach is to switch to the Session Authentication Module (SAM) that can replace forms authentication. There are important pros, including the fact that SAM recreates ClaimsPrincipal out of the cookie and roles are just Role claims:
// create cookie
SessionAuthenticationModule sam =
(SessionAuthenticationModule)
this.Context.ApplicationInstance.Modules["SessionAuthenticationModule"];
ClaimsPrincipal principal =
new ClaimsPrincipal( new GenericPrincipal( new GenericIdentity( "username" ), null ) );
// create any userdata you want. by creating custom types of claims you can have
// an arbitrary number of your own types of custom data
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role1" ) );
principal.Identities[0].Claims.Add( new Claim( ClaimTypes.Role, "role2" ) );
var token =
sam.CreateSessionSecurityToken(
principal, null, DateTime.Now, DateTime.Now.AddMinutes( 20 ), false );
sam.WriteSessionTokenToCookie( token );
From now on, the identity is stored in a cookie and managed automatically and, yes, the Authorization attribute on your controllers works as expected.
Read more on replacing forms module with SAM on my blog:
http://www.wiktorzychla.com/2012/09/forms-authentication-revisited.html

Form Authentication : Roles (MVC 4) C#

I am trying to implement Forms Authentication in my Application. I various examples and looked at the samples and questions provided in this forum and ASP.net MVC but I just can't get it to work.
I manage to authenticate my user but the roles does not seem to work :-(
I have setup my Web.Config as follow :
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
In my Controller I set the Index page to AllowAnonymous and then check in there if the user is authenticated. If not then redirect to the login page..
[AllowAnonymous]
public ActionResult Index(string sortOrder, string searchString,string currentFilter, int? page)
{
if (!Request.IsAuthenticated)
{
return RedirectToAction("Login", "Account");
}
//Find all the employees
var employees = from s in db.Employees
select s;
//Pass employees to the view (All works fine)
return View(employees.ToPagedList(pageNumber, pageSize));
}
This all is working 100%
My Login code looks like this :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login(User user, string returnUrl)
{
var myUser = db.Users.Where(b => b.UserName == user.UserName).FirstOrDefault();
if(myUser != null)
{
if(myUser.Password==user.Password)
{
//These session values are just for demo purpose to show the user details on master page
//Session["User"] = user;
ICollection<UserAccessLevel> levels = db.UserAccessLevels.Where(b => b.UserId == myUser.UserId).ToList();
//Session["levels"] = levels;
//Let us now set the authentication cookie so that we can use that later.
FormsAuthentication.SetAuthCookie(user.UserName, false);
return RedirectToAction("Index","Employee");
}
}
ViewBag.Message = "Invalid User name or Password.";
return View(user);
}
I also have the following code in the Global.asax file :
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
if (FormsAuthentication.CookiesSupported == true)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
//let us take out the username now
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
string roles = string.Empty;
using (TrainingContext entities = new TrainingContext())
{
User user = entities.Users.SingleOrDefault(u => u.UserName == username);
roles = "admin";//user.Roles;
}
//Let us set the Pricipal with our user specific details
e.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.Split(';'));
}
catch (Exception)
{
//somehting went wrong
}
}
}
}
When I log in my FormsAuthentication_OnAuthenticate executes and everything looks good. My User is set and my roles in the session is also there...
But when I click on the details of my Employee/Index screen it takes me back to the login screen (I expect it to take me to the details of the employee I clicked because I am logged in and I am setup as an admin role)
Please can you assist me to try and get to the problem. I sat for more than 18 hours already trying to figure this out.
I already looked at these solutions and as you can see most of my code comes from there...
codeproject.com/Articles/578374/AplusBeginner-27splusTutorialplusonplusCustomplusF
codeproject.com/Articles/342061/Understanding-ASP-NET-Roles-and-Membership-A-Begin
codeproject.com/Articles/408306/Understanding-and-Implementing-ASP-NET-Custom-Form
in case you need more detail about my code you can also download it from GitHub
https://github.com/Ruandv/Training/tree/FormsAuthentication
I will appreciate your assistance.
If you go to your Database and look for the table that assigns roles to users (probably generated by SimpleMembership?), does your user have an "admin" role?
Looks like you're only assigning the role in the FormsAuthentication_OnAuthenticate method without actually setting it in the DB.
// Your method (...)
User user = entities.Users.SingleOrDefault(u => u.UserName == username);
roles = "admin";//user.Roles;
And, although I'm not entirely sure, [Authorize(Roles = "admin")] may be using your Role Provider and checking if the user has/doesn't have the role in the database.