1

This tutorial

Create an ASP.NET Core app with user data protected by authorization on learn.microsoft.com

Teaches us to use Roles to handle authorization.

However, with regard to Roles, there is one thing I noticed:

If a user is added to a Role while they are logged in, the changes don't seem to be visible until they log out.

That is: If I call this while this particular identityUser is logged in:

userManager.AddToRoleAsync(idenityUser, role)

Then the operation shown in the tutorial to check if the current user is in a role:

context.User.IsInRole(role)

Returns FALSE even if User refers to this particular identityUser. And it keeps returning false until the user logs out and in again.

I actually tried to enhance the app in the tutorial to add a Promote functionality, that is, allow admins to promote regular users to Managers. And yes, the user being promoted had to log out and in to eventually become a Manager after he was promoted.

Now requireing users to do this when they are being promoted seems to be an unecessary inconvenience. But for issues like laying down banhammer this is, obviously, unacceptable. The banned user likely won't be kind enough to log out promptly.

Is there any way to force adding a user to a Role to be effective immediately, even if the said user is logged in at the moment?

3 Answers3

0

Unfortunately the user will need to log out then back in again to refresh their roles as they are stored as claims.. After you have updated their roles you could just invalidate their login & this would force them to sign in again:

How to sign out other user in ASP.NET Core Identity

MWD
  • 1,632
  • 2
  • 18
  • 39
0

I went through a similar scenario in a Node.js app using JWT authentication. In that case, I had set up an HTTP pipeline interceptor that would refresh the JWT token if certain user claims had changed.

With that in mind, I found this other stack overflow post. It looks like you do the same thing I did above. You adjust the claims on the user's principal and call the authentication manager to reauth. You should be able to do all that without the user ever being aware that he was just logged out/in.

clenard
  • 188
  • 2
  • 16
0

The correct answer is to use UserManager.IsInRoleAsync(identityUser, role) (docs) instead of ClaimsPrincipal.IsInRole(role) (which is `User.IsInRole(role) in a Razor view an Context.User.IsInRole(role) in aSignalR hub).

IdentityUser can be obtained from ClaimsPrincipal via UserManager.GetUserAsync(user) (docs).

So, the full code would be something like this. In Razor:

var user = await userManager.GetUserAsync(User);
if (user != null)
    if(await userManager.IsInRoleAsync(user))
        // ...

In SignalR hub:

var user = await userManager.GetUserAsync(Context.User);
if(user != null)
    if(await userManager.IsInRoleAsync(user))
        // ...

(uncertain if the null checks are necessary, but they won't hurt)

This info comes straight from the devs.

  • This is really not the solution. It works because you are misusing UserManager and roles. I mean, is banned a role? And how will you solve it when your apps don't have access to the identity database? I don't want to give apps access to the users database, because that is not their concern. And why? Only to read the actual permissions? So the solution is to add authorization tables and use resource-based authorization to read the actual permissions from those tables. You can add an AuthorizationFilter to handle banned users. –  Sep 28 '18 at 17:37
  • @RuardvanElburg What can I do? As I said, this info comes straight from the devs. Is it my place to argue with them? Besides: I'm not sure if I understand you correctly: What do you mean by "authorization tables"? Another tables in the database? –  Sep 28 '18 at 20:56
  • In the end you'll have to call a database to read the permissions (authorization). As the role itself (as present in the context.User) doesn't suffice, as you've noticed. My point is that roles shouldn't be used like that. And when you have to call the database, you can as well add a new table where it fits better. No need to add roles to a Identity. –  Sep 28 '18 at 21:20
  • When you keep authorization close to the resource (what you want to protect), then you'll have better control. As the resource is the place to add resource-based authorization. Use policies, where policies can be seen as business rules. The policies actually handle authorization, where claims, roles or custom authorization can be used as requirement. Or add AuthorizationFilters as an alternative. –  Sep 28 '18 at 21:28
  • And that way you have nothing to do with roles or claims that need to be updated. Basic access is granted, but the authorization of the resource is handled there. –  Sep 28 '18 at 21:30
  • Of course your solution works for you, and given that this is a small change that's fine. But consider the design I'm proposing. As for me this won't work. All my contexts are strictly seperated and that's why I'm using an authorization server. –  Sep 28 '18 at 21:37
  • @RuardvanElburg Why would being banned be any less of a role than being a Manager or Admin? (both examples explicitely given in the documentation) Forgive me if I'm wrong, but I have feeling your criticism mostly against the mechanism of roles, as implemented in the framework. But even if the mechanism of roles itself is 'wrong', this doesn't invalidate answers how to use it, does it? (Unless, of course, the only correct answer is "Don't use Roles, instead implement authorization yourself the way it should be implemented in the first place) Or am I misreading you? –  Sep 29 '18 at 17:29
  • You're right about my criticism about roles. IMO roles are useless for larger contexts (multiple resources, websites, seperate databases). But that's not the reason I think your answer isn't the solution. The reason is that there's a difference between the roles. A Manager or Admin role grants access to a resource, while being banned denies access. When the role is omitted you have access to the resource, while when the admin or manager role is missing you don't. It breaks how roles are used for authorization. Consider this: [Authorize("banned")]. That's why I think banned doesn't fit as role. –  Sep 29 '18 at 20:49
  • Furthermore there's a matter of interpretation. When they say 'Check on the server whether they have privileges rather than trusting the cookie' and 'hit your db if you want to ensure they have permissions.' then there actually isn't a difference with my suggestion. I suggest you add an additional table to register banned users, while they never say you should use the UserManager for that. –  Sep 29 '18 at 20:50
  • @RuardvanElburg I asked them precisely: "Just to make certain though, do I understand You correctly that I should use UserManager.IsInRoleAsync instead of ClaimsPrincipal.IsInRole?" - and the answer is, "That hits the database yes so it is guaranteed to be correct" - so I don't think I misinterpret what they say –  Sep 29 '18 at 20:52
  • Also Roles don't handle authorization. Policies do and claims or roles can be a requirement. Having a 'banned' role doesn't deny access just because it's a role, but because it's a requirement of the BannedUser policy. In which case there's no need to use a role to mark a user as banned. Things will become a lot easier when concerns are seperated. E.g. when banned isn't part of the ClaimsIdentity you don't have to worry about updating claims. You can proceed with using Identity the way you did, using IsInRole as this works as expected, without having the need to instantly detect claim changes. –  Sep 29 '18 at 20:52
  • IMO they only confirmed it hits the database. –  Sep 29 '18 at 20:59
  • So I don't say you shouldn't use Roles at all, just not in this case. There are better alternatives IMO to implement it. –  Sep 29 '18 at 21:32