8

I am having trouble getting ASP Identity to refresh its Identity stored in a cookie on demand.

In the Startup.Auth.cs file the cookie is set to regenerate as follows:

app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<QuizSparkUserManager, QuizSparkUser, int>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentityCallback: ((manager, user) => manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie)),
                    getUserIdCallback: ((claimsIdentity) => int.Parse(claimsIdentity.GetUserId())))
                }
            });

However I cannot work out how to refresh the contents on User.Identity in code, i.e. force a refresh of the identity cookie when I need it to refresh.

I want to be able to use the regenerate identity callback programmatically, is this possible?

My problem is similar to this one : How to invalidate .AspNet.ApplicationCookie after Adding user to Role using Asp.Net Identity 2?

However I want to refresh rather than invalidate the cookie.

Edit


After looking at the linked question I attempted the following (without full error handling):

IOwinContext context = Request.GetOwinContext();
QuizSparkSignInManager manager = context.Get<QuizSparkSignInManager>();
ClaimsIdentity newIdentity = manager.CreateUserIdentity(manager.UserManager.FindById(User.Identity.GetUserId<int>()));

AuthenticateResult authenticationContext =
                    await context.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ApplicationCookie);

if (authenticationContext != null)
{
    context.Authentication.AuthenticationResponseGrant = new AuthenticationResponseGrant(
                        newIdentity, authenticationContext.Properties);
}

bool first2 = User.IsInRole("Turtle");

Edit2: However the User still does not appear to refresh. On page reload they do seem to refresh, am I right in thinking this is because User.Identity cookie is part of the request and cannot be changed in code?

Community
  • 1
  • 1
Underscore
  • 1,017
  • 2
  • 10
  • 26

1 Answers1

11

If you are trying to add new role to already logged-in user, you need to sign user out. Then create new identity with new role and sign user in with the new identity. That's the only way to update the cookie.

Best place to check if user properties have changed are in callback you already use: CookieAuthenticationProvider.OnValidateIdentity. Something like this.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    // other stuff
    Provider = new CookieAuthenticationProvider
    {
        // this function is executed every http request and executed very early in the pipeline
        // and here you have access to cookie properties and other low-level stuff. 
        // makes sense to have the invalidation here
        OnValidateIdentity = async context =>
        {
            // invalidate user cookie if user's security stamp have changed
            var invalidateBySecirityStamp = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager));
            await invalidateBySecirityStamp.Invoke(context);

            if (context.Identity == null || !context.Identity.IsAuthenticated)
            {
                return;
            }
            if(/*Need to update cookie*/)
            {
                // get user manager. It must be registered with OWIN
                var userManager = context.OwinContext.GetUserManager<UserManager>();
                var username = context.Identity.Name;

                // get new user identity with updated properties
                var updatedUser = await userManager.FindByNameAsync(username);

                // updated identity from the new data in the user object
                var newIdentity = updatedUser.GenerateUserIdentityAsync(manager);

                // kill old cookie
                context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);

                // sign in again
                var authenticationProperties = new AuthenticationProperties() { IsPersistent = context.Properties.IsPersistent };
                context.OwinContext.Authentication.SignIn(authenticationProperties, newIdentity);
            }
        }
    }
});  

Disclaimer - never tested it, not even tried to compile it.

Also can see my other answer for reference - pretty much the same piece of code, but different goal.

UPD: Regarding another part of the question - how to detect a role change:
I can think of a way - have another GUID on a user record. Similar to SecurityStamp, but not used by the framework. Call it MySecurityStamp. On sign-in add value of MySecurityStamp to the cookie as a claim. On every request compare value of MySecurityStamp in the cookie to the value in the database. If values are different - time to regenerate the identity. And on every new role added/removed modify MySecurityStamp for the user in the database. This will cover all the sessions in all the browsers.

Community
  • 1
  • 1
trailmax
  • 34,305
  • 22
  • 140
  • 234
  • At the moment I only regenerate the identity and sign in, do you happen to know if the extra sign out is necessary to kill the cookie or will it be invalidated by the call to ```Authentication.AuthenticationResponseGrant```? – Underscore Oct 27 '14 at 11:44
  • Why are you using `Authentication.AuthenticationResponseGrant`? I don't think this should be consumed by anything other than OWIN itself. And to kill the cookie you need to use `Authentication.AuthenticationResponseRevoke` (or something like this, not sure about the naming). Anyway, just use `SignOut` - does the responses and grants for you in the background. – trailmax Oct 27 '14 at 14:15
  • From the docs it seems like ```AuthenticationResponseGrant``` and ```AuthenticationResponseRevoke``` are just strongly typed accessors for ```SignIn``` and ```SignOut``` but I take your point and thanks for taking the time to run through this. – Underscore Oct 27 '14 at 14:50
  • @trailmax This solution worked really fine but I'm stuck at something: How can I know in this part of the code if the entity has changed or if I have added a role/claim to it? There is the if statement `if(/*Need to update cookie*/)` but do you have any ideas on which options are available in order to know if I have to do it or not? – Felipe Correa Sep 10 '15 at 16:20
  • 1
    @FelipeCorrea one way to do this is to put a boolean flag on user `NeedUpdating` and instead of `/*Need to update cookie*/` check for the flag on the user object. And once re-signed-in, set this flag to `false`. Problem with this approach if user has more than one session open - it'll only update the cookie in one browser. – trailmax Sep 10 '15 at 16:26
  • 1
    @trailmax Thanks for the answer! That's a huge problem though... I guess the same problem exists if I sign out the user and sign him again from code, right? Any way this can be achieved covering all browsers sessions? I'm really concerned about how a simple requirement like this one is not implemented natively in the manager itself like `manager.RegenerateCookieAsync()`. Please, if you can make any suggestion for another approach it would be really appreciated! – Felipe Correa Sep 10 '15 at 16:33
  • @FelipeCorrea That's not a simple task and not a the best from the security point of view - it is common to ask log user out and ask to re-login when their account details have changed. – trailmax Sep 11 '15 at 07:42
  • @FelipeCorrea I've added an update to the question regarding detection of roles changed. – trailmax Sep 11 '15 at 07:46
  • @trailmax Awesome suggestion, but that would mean I'd have to hit the database on every request, right? If I have to hit the database to compare that stamp, wouldn't it be easy to bring the new identity and change the cookie on every request? – Felipe Correa Sep 11 '15 at 15:54
  • 1
    @FelipeCorrea You don' have to hit the DB on every request - you can set it to be every couple minutes or so - depending how fast you'd want the changes to reflect on user. And recreating the cookie every time is more expensive than one DB hit. I did the timing once and recreating a cookie is a considerable effort due to the encryption taking place. – trailmax Sep 11 '15 at 15:58