Role 'permissions' stay when changing role - c#

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.

Related

With ASP.NET Identity is it possible to add and delete claims dynamically during an existing session?

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/

Can I use the Authorize attribute filtering by Roles and Claims when using ASP.NET Identity and JWT authentication from external providers?

So I’m using firebase for authentication on my WebAPI app. It gets a JWT token and then authenticates it and then puts the claims in the HttpContext.User.
My only problem is I just want to use Firebase Authentication for authentication, not for authorization. I want to use ASP.NET Identity for this.
so right now, when someone connects to my server, I’ll check if they don’t have an account, if not I will create one for them.
var name = _contextAccessor.HttpContext.User.Claims.First(c => c.Type == "user_id").Value;
ApplicationUser user = await _userManager.FindByNameAsync(name);
if (user == null)
{
user = new ApplicationUser(name);
await _userManager.CreateAsync(user);
}
So this works, and now there is a record in the ASPNetUsers against the user and later on, I can give it Claims and Roles against whatever business rules I’d like.
However, my question is, previously, when I’ve used ASP.NET Identity I’ve been able to leverage all of the built-in features like the Authorize attribute to do my authorization.
So if both authentication and authorization is done using ASP.NET Identity, I can write
[Authorize(Roles = "Administrator")]
Obviously, that won’t work with external authentication because HttpContext.User is the Firebase Authenticated user, not the corresponding ASP.NET Identity user that has the Administrator role.
Is there a way to customize the existing Authorize attribute to configure it to somehow convert my firebase token into an ASP.NET Identity so that it would recognize the roles and claims it has or if I wanted to do all this through middleware, am I going to need to write my own authorize attribute?
Ok so I've finally figured it out. It seems there's a couple of events you can intercept during the Jwt authentication process. In particular, there is an OnTokenValidated event.
services.AddJwtBearer(options =>
{
...
options.Events = new JwtBearerEvents
{
OnTokenValidated = async ctx =>
{
// 1. grabs the user id from firebase
var name = ctx.Principal.Claims.First(c => c.Type == "user_id").Value;
// Get userManager out of DI
var _userManager = ctx.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
// 2. retrieves the roles that the user has
ApplicationUser user = await _userManager.FindByNameAsync(name);
var userRoles = await _userManager.GetRolesAsync(user);
//3. adds the role as a new claim
ClaimsIdentity identity = ctx.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
foreach (var role in userRoles)
{
identity.AddClaim(new System.Security.Claims.Claim(ClaimTypes.Role, role));
}
}
}
};
});
So what the code above is saying
once the token is authenticated, take the userId from the
external provider
Go into ASP.NET Identity and find the user and
the roles for that user [remember in my original question, I insert a user
into ASP.NET Identity table when they first log in, I'm just grabbing that user]
Insert the roles of that user back into the ClaimsIdentity
The result is that when the Authorise attribute runs, it'll include the AspNET Identity Roles of the user in the check and I can do something like below and it'll check the role.
[Authorize(Roles = "Administrator")]
Yes you can use the authorize attribute with some modification,
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,Roles = "Administrator")]
But if you want to make it a default scheme, then refer to this answer
Here is a summary, Add it to he Startup class,
services.AddAuthentication(cfg =>
{
cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
I think this should make your Authorization work:
You have to make ClaimsPrincipal the base class of your ApplicationUser class.
(ClaimsPrincipal implements the IPrincipal interface)
Then inside your ApplicationUser class implement/override the IsInRole method that comes with the ClaimsPrincipal (base) class. IsInRole official documentation. The role argument of the method will contain the value of the Roles property on the AuthorizeAtribute that you specify.
Set the HttpContext.User using your instance of type ApplicationUser. (explanation: Setting the property User on Httpcontext allows you to specify (=customize) the user of the current request within asp.net request processing)

Is Claims Better Way to go rather than Roles

I am building a website(front end reactjs, backend asp.net web api core 2) and trying to figure how to do authentication/authorization.
For authentication I pretty much will JWTBearer tokens, if the username and password match what I got for the user in my db, give them a token.
It is for authorization that I am not sure about, The last time I had to do something it was more have roles in your database and then check if that user had that role or not when they tried to access something.
Now I was looking at this blog and the author talks about
Rather than try and store all the “roles” that a user might have (e.g. administrator, user, super user) you can store information about the user as claims.
I am confused about this, how does this work. Say I need one user to be able to see the very secret area of my site(ie admin area) but another user who is also logged in can't see it as he is a general user.
What information is used to make this claim? What is being stored in the database?
With roles, you could have something like User can have a Roles (ie Admin) and then just check that when they try to do something.
I am also wondering how would the front end know what to hide(ie the link that says "admin area") as can you send back a result saying that they have a claim that allows them to see the admin area?
Claims can be used to manage a wider array of use cases than Role, particularly when paired with Policies and Requirements. Two examples that come to mind:
Requiring a user have a specific reputation in order to edit a record
Requiring a user to be at least 21 to order a particular product
One could create an "Over21" role, and a "Over1000Reputation" role, but maintaining role membership becomes unwieldy; you need to set up jobs to add (or remove) users from those roles as their age or reputation changes.
Instead, have your JWT include a Reputation and BirthDate claims:
identity.AddClaim(new Claim("Reputation", 722));
identity.AddClaim(new Claim("BirthDate", new DateTime(2000, 1, 1)));
Your code could then implement a Requirement and AuthorizationHandler that calculates whether the user should be authorized based on these claim values:
public class ReputationRequirement : IAuthorizationRequirement
{
public int Reputation { get; private set; }
public ReputationRequirement (int reputation)
{
Reputation = reputation;
}
}
Paired with an AuthorizationHandler along these lines:
public class ReputationHandler : AuthorizationHandler<ReputationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ReputationRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == "Reputation"))
{
return Task.CompletedTask;
}
var reputation = context.User.FindFirst(c => c.Type == "Reputation").Value as int;
if (reputation >= requirement.Reputation)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
In your startup's ConfigurationServices, you can give a name (Policy) to a particular instance of your requirement:
services.AddAuthorization(options =>
{
options.AddPolicy("Reputation750", policy => policy.Requirements.Add(new ReputationRequirement(750)));
options.AddPolicy("OldAndWise", policy =>
{
policy.Requirements.Add(new ReputationRequirement(750));
policy.Requirements.Add(new MinimumAgeRequirement(40));
}
});
Lastly, you can mark MVC controllers or methods with a policy:
[Authorize(Policy = "OldAndWise")]
public class StackOverflowWineController : Controller
{
// omitted for brevity
}
Note the example linked to has more detail that this brief code snippet.
Regarding storage of claims in a database, that depends on the use case. When treating Roles as Claims, you would still have something like a UserRoles and RoleMembership tables. For Reptuation and BirthDate, it would probably make more sense to store them in a User table, rather than some sort of dedicated generic Claims table.
Roles can still be used like in the older versions. Claims are simply a way to store more user data and provide more flexibilities with the authentication. You can add them to a list like this:
List<Claim> claims = new List<Claim> {
new Claim(ClaimTypes.Name, "username"),
new Claim(ClaimTypes.Role, "First Role"),
new Claim(ClaimTypes.Role, "Second Role"),
new Claim(ClaimTypes.Role, "Third Role")
};
You can authorize any controller or action using [Authorize(Roles = "user, superUser")]. If you want to display something to a user based on roles, you can use #if (User.IsInRole("User")).
As of JWT creation, you can have a look at this class: https://github.com/neville-nazerane/netcore-jwt-sample/blob/master/website/TokenGenerator.cs
It has utility functions to create a JWT with a given user name and also one that takes in a username and a comma separated string of roles.

Can UserManager.GetUserAsync(User) return null in a class with Authorize attribute?

I am using Asp.net core 2.0 MVC with Individual User Account enabled. The automatically-generated ManageController class is attributed by [Authorize].
I find there are some action methods with the following code snippet.
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
Question
In my mental model, being authorized guarantees being a registered user. So such a null checking in authorized classes seems to be unnecessary. I want to know whether or not UserManager.GetUserAsync(User) can return null in a class with Authorize attribute?
I want to know whether or not UserManager.GetUserAsync(User) can return null in a class with Authorize attribute?
It can, if the user entry was removed from the database after the user logged in (by default, cookies are validated after 30 minutes so they can still be "valid" even after the corresponding user was removed from the database).

How to give user access in MVC C#?

I want to give the user access according to the user role.
I have two user roles. they are Admin and user.
I write my controller like this.
[Authorize(Roles = "Admin")] // my Problem is here. I don't know how to set the current user role
public ActionResult Index()
{
var query = from company in db.tblCompanies
select company;
return View(query.ToList());
}
But I don't know how to set the Roles = "Admin" after cutomer login.
In my user tale I have Roles coloum and it can save Admin or user.
But I don't know how to set and where should I set Roles = "Admin".
Based on your question what I get is you want to set the currently logged user to some role. So here is my answer to that.
To Add a User to a Role:
Roles.AddUserToRole(userName, roleName);
To Remove a User from a Role:
Roles.RemoveUserFromRole(userName, roleName);
Reference Links:
SO - How to Assign roles to User while creating their account
MSDN - Implementing a Custom Role Provider
MSDN - Roles.AddUserToRole Method
MSDN - Roles.RemoveUserFromRole Method
Take a look at this :
http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-7
You basically assign roles to your users via the ASP.NET Configuration website. Once done the user - role mapping is handled by default.

Categories

Resources