Can someone describe to me just how you delete roles with asp.net identity. I tried the following, but it did not work and I received a Specified Method is not supported error:
public async Task DeleteRole(string role)
{
// delete role
var roleStore = new RoleStore<IdentityRole>(new Context());
await roleStore.DeleteAsync(new IdentityRole(role));
}
Not sure if this is referring to something with my async logic, or specifically with asp.net identity itself. Nevertheless can someone demonstrate to me how to make this work correctly. There is virtually no documentation available on the new identity system for asp.net at this time.
The Identity context (IdentityDbContext) contains the role store. So you would (assuming AppDb is your context):
var role = AppDb.Roles.Where(d => d.Name == "my role name").FirstOrDefault();
AppDb.Roles.Remove(role);
AppDb.SaveChanges();
You basically treat it as a normal EntityFramework DbSet, it's inherited from the IdentityDbContext.
I know that is an old question, but I've tried a way that doesn't touch store (or DbContext) directly.
I've used it in ASP.NET Core 2.1
var role = await _roleManager.FindByNameAsync(roleName);
var result = await _roleManager.DeleteAsync(role);
needless to say that it:
delete role that assigned to user -> AspNetUserRoles
delete role's claims -> AspNetRoleClaims
delete role itself -> AspNetRoles
Related
I'm trying to add and delete claims (roles in this case) dynamically after a successful login using Identity 2 in Asp.Net 4.5.2. My application has an authentication database which contains the AspNetUsers, AspNetRoles and AspNetUserRoles tables etc and a number of other databases. During the course of a user session my users can switch between the other databases and their current claims (roles) are modified based on which database they are currently using. I'd like, therefore, to add and delete claims throughout the session. This allows me to modify which views the user has access to based on their current authorizations.
I've done many days of research on this in stack overflow and the MS Identity help pages, such as they are, and can find nothing similar to what I'm attempting to do. Based on what I've learned I've been able to add my own new claims but only during the login process, changing them at any other point works for that request but the changes do not persist and are lost by the time the next request comes in.
As far as I can tell when the claims are added during login they are encoded within the session cookie and when I add them at any other point the cookie is not modified. From my current understanding this is happening in the Identity module of the OWIN pipeline. The method where I've successfully added claims is the GenerateUserIdentityAsync method in ApplicationUser (derived from IdentityUser.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim("Customer_ID", Convert.ToString(this.Customer_ID)));
userIdentity.AddClaim(new Claim("LastName", this.LastName));
userIdentity.AddClaim(new Claim("FirstName", this.FirstName));
userIdentity.AddClaim(new Claim(ClaimTypes.Role, "TestRole", null, null, "TestIssuer"));
return userIdentity;
}
Unfortunately, this doesn't help my case because I'm attempting to add/delete the claims outside the OWIN pipeline.
Is what I'm trying to do possible at all or am I going about this entirely the wrong way? I would have thought dynamic modification of authorizations is not that unusual.
Some of the many stack overflow questions I've looked at include:
How to add claims in ASP.NET Identity,
Dynamic User Claims in ASP.NET Identity EF and
ASP.NET Identity and Claims.
None of them quite cover what I'm attempting to do.
In Asp.net :
Adding claims to existing identity seems like a small task to accomplish. But, well, it doesn’t go so easy. We can build a middleware class and try something like shown here.
foreach(var role in user.Roles)
{
var claim = new Claim(newIdentity.RoleClaimType, role.Name);
identity.AddClaim(claim);
}
And in Asp.net core:
There’s the correct way to edit existing identity and it’s called claims transformation. Basically, we have to write a custom class that implements the IClaimsTransformation interface. Documentation doesn’t give much information about it but the most important thing is said – we need to clone the given identity.
In short, here’s how the process goes:
Clone current user identity
Add custom claims
Return cloned identity
public class AddRolesClaimsTransformation : IClaimsTransformation
{
private readonly IUserService _userService; public class AddRolesClaimsTransformation : IClaimsTransformation
{
private readonly IUserService _userService;
public AddRolesClaimsTransformation(IUserService userService)
{
_userService = userService;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
// Clone current identity
var clone = principal.Clone();
var newIdentity = (ClaimsIdentity)clone.Identity;
// Support AD and local accounts
var nameId = principal.Claims.FirstOrDefault(c => c.Type ==
ClaimTypes.NameIdentifier || c.Type == ClaimTypes.Name);
if (nameId == null)
{
return principal;
}
// Get user from database
var user = await _userService.GetByUserName(nameId.Value);
if (user == null)
{
return principal;
}
// Add role claims to cloned identity
foreach (var role in user.Roles)
{
var claim = new Claim(newIdentity.RoleClaimType, role.Name);
newIdentity.AddClaim(claim);
}
return clone;
} }
The final thing to do is to register claims transformation with dependency injection in the ConfigureServices() method of the Startup class.
services.AddScoped<IClaimsTransformation, AddRolesClaimsTransformation>();
enter code here
Based on https://gunnarpeipman.com/aspnet-core-adding-claims-to-existing-identity/
I have an ASP.NET core MVC site with a SQLite data connection using ASP.NET core authorization like this:
// Startup.ConfigureServices
services.AddAuthorization(e =>
{
e.AddPolicy(Policies.UserRead, b => b.RequireRole("Editor", "Root"));
}
This is one of the policies which restricts access to site with user information. (Policies.UserRead is a constant string). This policy is then applied to the view like this:
[Authorize(Policies.UserRead)]
public async Task<IActionResult> Index()
{
}
This works great and only users with the role Editor or Root can access the view. But problems arises when the role of a user is changed while logged in. e.g.
User A (role Editor) logs in and uses Index() - success
User B (role Root) logs in and removes the role Editor from user A
User A uses Index() - still success
You would expect that user A can not access Index() anymore because he no longer as the role Editor. Buuuut he still can - as long as he does not log out and log back in again, because relogging fixes this issue. It seems like somebody (I think ClaimsPrincipal is the culprit here) cached the role - which would be OK if I knew how to invalidate the cache...
Role changing code:
// get the user whos role is changed
var targetUser = await _context.Users.SingleOrDefaultAsync(m => m.Id == model.Id);
if (targetUser == null) return NotFound();
// get the user who changes the role
var sourceUser = await _userManager.GetUserAsync(User);
if (sourceUser == null) return RedirectToAction("Index");
// remove the current role
await _userManager.RemoveFromRoleAsync(targetUser, targetUser.Role.ToString());
// add to the new role
await _userManager.AddToRoleAsync(targetUser, model.Role.ToString());
// update & save the changes
_context.Update(targetUser);
await _context.SaveChangesAsync();
This is basically the code I use to change the role of a user (I cut out the view/model parts because they are irrelevant). Notes:
targetUser and sourceUser are both ApplicationUser (which implements IdentityUser).
_userManger is - who would have thought - of the type UserManager<ApplicationManger>
I tried to relog the user using SignInManger<> but it seems like you can only log out the current user - which would be the user changing the role and not the user whose role would be changed.
What am I missing? It would be nice if the user would not have to do anything (e.g. sign back in) in order to "refresh" the user role.
The problem is that the user's role claims are stored in the cookie(default implementation of aspnet identity) so unless the user sign out even if the user's roles change, authorization result does not change. The solution is to use ValidateAsync event. Example exists in the official docs.
Another possible solution is to exclude role claims from cookie and use claims transformation.
For this you need to override the CreateAsync method of UserClaimsPrincipalFactory see this article how to change claims. Then you can use claims transformation to add role claims.
In our project management application, we need to keep access rights (per project for each user) in a separate table in db. IE.
User A can create tasks in Project #1,
User A can only read task in Project #2
To succeed that, we extend our AspNetUserRoles table by adding a new coloumn(projectID).
By default, in razor view, we can check user roles with User.IsInRole("CanCreateTask"). We need to extend this method and want to check the user role for specific project Ie. User.IsInRole("CanCreateTask", project.Id)
EDITED: Also i need to check user access in controller with project.Id
[Authorize(Roles = "CanCreateTask")]
Did some research on net but could not find any solution.
You could create an extension method, extending the User class.
public static bool IsInRole(this User user, string role, int projectID)
{
var isInRole = user.IsInRole(role);
var hasRoleInProject = // Logic for deciding if it is in the role for this project
return isInRole && hasRoleInProject;
}
This method would be called like this:
user.IsInRole("CanCreateTask", project.Id);
I have a MVC Wab API and I've started modifying it to include some of my own tables using a code first approach.
I'm having trouble with seeding the AspNetUsers table with initial data which then works with my (the default) token based authentication.
When I seed the database, the SecurityStamp field is not populated and it seems this is critical to the token based authentication.
Registering an account via the AccountController generates this fine, however when I see the table via the code below, the security stamp is not populated...understandably so.
context.Users.Add(new ApplicationUser
{
Email = "damo2#email.co.uk",
UserName = "damo2#email.co.uk",
PasswordHash = new PasswordHasher().HashPassword("Som3Pass!")
});
There appears to be a UpdateSecurityStampAsync method which I assume will generate the Security Stamp, but I can't figure out how to call / used it while seeding. (I don't know what to do with here..I'm still a novice)
Questions are:
What is the correct way to seed the user table when using MVC WebAPI & Individual accounts.
If I should be using the UpdateSecurityStampAsync method, how?
For question 2, The steps are:
Add / Update the users in the context.
Create a UserStore. The UserStore is basically the data layer wrapper for ASP Identity. It controls how ASP Identity works with the database.
Create a UserManager. The UserManager is basically the implementation of ASP Identity. ASP Identity "out of the box" contains many methods, most of which are empty (exp: SendSMS, VerifyTwoFactorToken). The developer is supposed to implement the methods they care about.
However, the UpdateSecurityStamp method is one of the methods with a default implementation. So we luck out.
Call UserManager.UpdateSecurityStamp(UserJustUpdated.Id).
public override void Seed(ApplicationDbContext context)
{
//The UserStore is ASP Identity's data layer. Wrap context with the UserStore.
UserStore<ApplicationUser> userStore = new UserStore<ApplicationUser>(context);
//The UserManager is ASP Identity's implementation layer: contains the methods.
//The constructor takes the UserStore: how the methods will interact with the database.
UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(userStore);
//Add or Update the initial Users into the database as normal.
context.AddOrUpdate(
x => x.Email, //Using Email as the Unique Key: If a record exists with the same email, AddOrUpdate skips it.
new ApplicationUser() { Email = "damo2#email.co.uk", UserName = "damo2#email.co.uk", PasswordHash = new PasswordHasher().HashPassword("Som3Pass!") },
new ApplicationUser() { Email = "2ndUser#email.co.uk", UserName = "Jane Doe", PasswordHash = new PasswordHasher().HashPassword("MyPassword") }
);
//Save changes so the Id columns will auto-populate.
context.SaveChanges();
//ASP Identity User Id's are Guids stored as nvarchar(128), and exposed as strings.
//Get the UserId only if the SecurityStamp is not set yet.
string userId = context.Users.Where(x => x.Email == "damo2#email.co.uk" && string.IsNullOrEmpty(x.SecurityStamp)).Select(x => x.Id).FirstOrDefault();
//If the userId is not null, then the SecurityStamp needs updating.
if (!string.IsNullOrEmpty(userId)) userManager.UpdateSecurityStamp(userId);
//Repeat for next user: good opportunity to make a helper method.
userId = context.Users.Where(x => x.Email == "2ndUser#email.co.uk" && string.IsNullOrEmpty(x.SecurityStamp)).Select(x => x.Id).FirstOrDetault();
if (!string.IsNullOrempty(userId)) userManager.UpdateSecurityStamp(userId);
//Continue on with Seed.
}
As far as question 1, there are many ways. Which one is correct is a larger discussion. Most efficient? most secure? least work?
The main problem with the seeding approach are having the passwords as plain text. Your environment will determine if that is a serious concern or not.
var user = UserManager.Find(...);
ClaimsIdentity identity = UserManager.CreateIdentity(
user, DefaultAuthenticationTypes.ApplicationCookie );
var claim1 = new Claim(
ClaimType = ClaimTypes.Country, ClaimValue = "Arctica", UserId = user.Id );
identity.AddClaim(claim1);
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = true }, identity );
var claim2 = new Claim(
ClaimType = ClaimTypes.Country, ClaimValue = "Antartica", UserId = user.Id );
identity.AddClaim(claim2);
Both claim1 and claim2 are persisted across requests only for the time ClaimsIdentity user is logged in. In other words, when user logs out by calling SignOut(), the two claims are also removed and as such the next time this user logs in, it is no longer a member of these two claims ( I assume the two claims don't exist anymore )
The fact that claim2 is persisted across requests ( even though authentication cookie was already created when claim2 was added to the user ) suggests that claims don't get persisted across requests via authentication cookie, but via some other means.
So how are claims persisted across requests?
EDIT:
1) As far as I can tell, claims of type IdentityUserClaim are never persisted in a cookie?
var user = UserManager.Find(...);
/* claim1 won't get persisted in a cookie */
var claim1 = new IdentityUserClaim
{ ClaimType = ClaimTypes.Country, ClaimValue = "Arctica", UserId = user.Id };
user.Claims.Add(claim1);
ClaimsIdentity identity = UserManager.CreateIdentity(
user, DefaultAuthenticationTypes.ApplicationCookie );
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = true }, identity );
If my assumption is correct, is the reason why IdentityUserClaim instances aren't persisted in a cookie because it is assumed that these claims should be stored in a DB and as such could in subsequent requests be retrieved from a DB, while claims of type Claim usually aren't stored in a DB and hence why they need to be persisted in a cookie?
2)
If you'd like to have a deeper look how it all works, check out the
source code of Katana Project
I thought Asp.net Identity 2 was not part of the Katana project ( namely, I've seen people asking when will Microsoft release the source code for Asp.Net Identity, even though Katana source code is already available )?!
thank you
Good question. Even made me do a little experiment.
This line:
AuthenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = true }, identity );
Does not set a cookie. Only sets Identity object for the later callback.
Cookie is only set when the control is passed to middleware and some OWIN internal method called Response.OnSendingHeaders.
So your code is just adding claim2 on the identity object that is stored in memory for later user. In theory you can even set claim1 after you have done the AuthenticationManager.SignIn. And it will be persisted in the cookie anyway.
If you try to add a cliam like this in a controller:
public ActionResult AddNonPersistedClaim()
{
var identity = (ClaimsIdentity)ClaimsPrincipal.Current.Identity;
identity.AddClaim(new Claim("Hello", "World"));
return RedirectToAction("SomeAction");
}
This claim won't be set in the cookie and you will not see it in the next request.
If you'd like to have a deeper look how it all works, check out the source code of Katana Project, look on Microsoft.Owin.Security and Microsoft.Owin.Security.Cookies projects. Along with AuthenticationManager in Microsoft.Owin.Net45 project.
Update
To answer your Edit 1 - IdentityUserClaim is indeed persisted into the database and this is the way you can assign persisted claims to the user. You add these on the user through UserManager
await userManager.AddClaimAsync(userId, new Claim("ClaimType", "ClaimValue"));
This creates records in your database table that represents IdentityUserClaim. When next time user is logged in, these claims are read from the database and added to the identity and are available on ClaimsIdentity.Current via property .Claims or by method .HasClaim().
IdentityUserClaim does not do anything else - just way to serialise Claim object into the database. You don't usually access these directly, unless you want to go "bare knuckles" and write to that table yourself, outside of UserManager.
To put it another way - Identity does not set the cookie. OWIN creates the cookie. Have a look on this piece of code:
public async Task SignInAsync(IAuthenticationManager authenticationManager, ApplicationUser applicationUser, bool isPersistent)
{
authenticationManager.SignOut(
DefaultAuthenticationTypes.ExternalCookie,
DefaultAuthenticationTypes.ApplicationCookie,
DefaultAuthenticationTypes.TwoFactorCookie,
DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
DefaultAuthenticationTypes.ExternalBearer);
var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.Email, applicationUser.Email));
authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Here Authentication manager is part of OWIN. Identity is part of System.Security.Claims. All that belongs to Identity project is CreateIdentityAsync method - that is basically converts user from the database into ClaimsIdentity with all the persisted roles and claims.
To answer your Edit 2: You are correct, AspNet Identity is not part of Katana project, but Identity uses OWIN (part of Katana) for cookie handling and authorisation. Identity project mostly deals with user/roles/claims persistence and user management, like locking-out, user creation, sending emails with password resetting, 2FA, etc.
What was a surprise for me is that ClaimsPrincipal along with ClaimsIdentity and Claim are part of .Net framework that is available outside of OWIN or Identity. These are used not only in Asp.Net, but in Windows applications. Good thing that .Net now has open-source and you can browse through all these - gives you a better understanding how it all works together. Also if you are doing unit-testing, it is invaluable to know the internals, so you can stub-out all the functionality without using mocks.
If you are using AD authentication and asp core 2.1 or 2.2, there is an option OpenIdConnectOptions when we configure services named ClaimActions, with the help of ClaimActions you can write class [CustomClaimsFactory] which inherits ClaimActions also override its Run method which actually will set the persistent claims, please find code below :
/* startup.cs */ services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.ClaimActions.Add(new CustomClaimsFactory(
"userName",
"xxxxx#outlook.com"
));
}
/*CustomClaimsFactory run method*/ public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
{
identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));
}