ASP.NET Identity, add another user to role instantly (they don't have to log out and in again)
Asked Answered
I

3

13

First of all, I'm aware of this question: MVC 5 AddToRole requires logout before it works?

and this one: What is ASP.NET Identity's IUserSecurityStampStore<TUser> interface?

so please don't mark this as a duplicate.

I'm trying to add another user to a role (i.e. the user we're adding to the role is not the current user. If they are, the answer to first question I linked to is sufficient.)

Like so:

IdentityResult result = await userManager.AddToRoleAsync(userID, roleName);

The two situations I'm doing this in are: from an admin page, where the current user is the administrator; and a webhook secured with basic authentication (where there is no current user at all).

THE PROBLEM: if the user that this change applies to is logged in and using the app, I need the "add to role" change to apply instantly. They shouldn't have to log out and in again for the change to happen, and it needs to happen straight away.

Thanks everyone.

EDIT: By the way, User.IsInRole(roleName) requires logout and login to reflect being added to the new role. UserManager.IsInRole(userID, roleName) does not, because (I assume) it goes straight to the database tables to check. But if the user hits an action method secured with the role they've just been added to, they still have to log in again, which is fair enough. Still curious if there's a way around this.

EDIT: Here is the source code for the Authorize attribute: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/4e40cdef9c8a8226685f95ef03b746bc8322aa92/src/System.Web.Mvc/AuthorizeAttribute.cs

It uses User.IsInRole, which is essentially why we need to log in again. It seems the method to override is AuthorizeCore(HttpContextBase httpContext). I'm not brave or good enough to mess with this right now but if you want to have a go lots of people will find this helpful.

Instrumentalism answered 21/4, 2016 at 10:1 Comment(1)
i'm interested to know the answer to this question since I've wondered myselfHyperparathyroidism
S
9

Starting from the bottom of your question. User.IsInRole() goes into user cookie and checks what roles are stored in that cookie. Hence it requires relogin for changes to take effect. And yes, you are correct in saying that UserManager.IsInRole() checks with database, not with the cookie.

To make sure role changes are applied immediately you need to check for change in roles on every request. To do that in Startup.Auth.cs find this line:

OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
    validateInterval: TimeSpan.FromMinutes(0), // <-- This is zero. Check on every request
    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),

This is a framework's way of updating cookie. By default validateInterval is set for 30 minutes. If you set it to zero, the system will create a new cookie with updated roles on every request. This might be too much DB-load if you have enough users hitting your system at the same time. So I'd increase the timespan to 30-seconds-1-2minutes.

This feature was built as a way to logout all sessions by a single password change. But also works well for your purposes.

Sample answered 22/4, 2016 at 0:35 Comment(4)
Great answer! Already occurred to me when I found your answer here: #25878718, but as you say, database load. I guess just test and it's not a problem until it's a problem. But it seems a pretty extreme solution to avoid one little login. Thanks!Instrumentalism
Well, measure the load and profile the DB requests and decide if this is a benefit or a problem. You can set the time-interval to 5 minutes. Soon enough for roles to be updated from user's point of view and does not add that much extra load. Again, I'm talking about thousands of users at the same time... and this is a nice problem to have!Sample
this is not work in .Net Core. for .NetCore bottom answer is correct(write by Wolfspirit):services.Configure<SecurityStampValidatorOptions>(options => { options.ValidationInterval = TimeSpan.FromMinutes(1); });Arrowroot
@AliRasouli of course it won't work in Core, the answer is from 2016 - there was no Core at the time.Sample
B
8

In ASP.NET Core, SignInManager.RefreshSignInAsync() solves this.

Blackguardly answered 4/5, 2017 at 0:0 Comment(3)
I was googling for asp.Net framework, despite that, I will not use this answer, it just deserves an upvote.Loseff
Same here. Upvote from me. If/when I finally move my existing sites from Framework to Core, this will be so useful. Thank you. 👍Fizgig
This should be the accepted anwser, messing with the permission caching doesn't make sense, it affects performances, this is the best solution, thanks!Reginaldreginauld
S
5

For ASP.NET Core Identity 2 the solution is to use:

services.Configure<SecurityStampValidatorOptions>(options =>
{
    options.ValidationInterval = TimeSpan.FromMinutes(1);
});

To force an update every minute or use TimeSpan.Zero to force an update everytime the user accesses the page (notice that everytime a database request is performed).

Also make sure that if you overwrite the cookie events do not use:

        services.ConfigureApplicationCookie(options =>
        {
            options.Events = new CookieAuthenticationEvents(){
            ...
            };
        }

But overwrite the Events you need directly as otherwise validation is not called:

        services.ConfigureApplicationCookie(options =>
        {
            options.Events.OnRedirectToLogin = ctx => {
            ...
            };
        }
Slowly answered 1/4, 2019 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.