Custom Claims lost on Identity re validation

Problem solved (it seemms), I post my solution since I havn't found may appropriate answers and I think it might be useful to others.

The right track was found in an answer to the question Reuse Claim in regenerateIdentityCallback in Owin Identity in MVC5

I just had modify a little the code since the UserId in my case is of type string and not Guid.

Here is my code:

In Startup.Auth.cs

 app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),

            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  

                //OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                //   validateInterval: TimeSpan.FromMinutes(1d),
                //   regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager, DefaultAuthenticationTypes.ApplicationCookie))

                OnValidateIdentity = context => SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, string>(
                   validateInterval: TimeSpan.FromMinutes(1d),
                   regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager, context.Identity),
                   getUserIdCallback: (ci) => ci.GetUserId()).Invoke(context)

            },
            /// TODO: Expire Time must be reduced in production do 2h
            //ExpireTimeSpan = TimeSpan.FromDays(100d),
            ExpireTimeSpan = TimeSpan.FromMinutes(2d),
            SlidingExpiration = true,
            CookieName = "RMC.AspNet",
        });

NOTE: Please note that in my sample ExpireTimeSpan and validateInterval are ridiculously short since the purpose here was to cause the most frequest re validation for testing purposes.

In IdentityModels.cs goes the overload of GenerateUserIdentityAsync that takes care of re attaching all custom claims to the Identity.

    /// Generates user Identity based on Claims already defined for user.
    /// Used fro Identity re validation !!!
    /// </summary>
    /// <param name="manager"></param>
    /// <param name="CurrentIdentity"></param>
    /// <returns></returns>
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Re validate existing Claims here
        userIdentity.AddClaims(CurrentIdentity.Claims);


        return userIdentity;
    }

It works. Not really sure if it is the best solution, but in case anyone has better approaches please feel free to improve my answer.

Thanks.

Lorenzo

ADDENDUM

After some time using it I found out that what implemented in GenerateUserIdentityAsync(...) might give problems if used in conjunction with @Html.AntiForgeryToken(). My previous implementation would keep adding already existing Claims at each revalidation. This confuses AntiForgery logic that throws error. To prevent that I've re implemnted it this way:

    /// <summary>
    /// Generates user Identity based on Claims already defined for user.
    /// Used fro Identity re validation !!!
    /// </summary>
    /// <param name="manager"></param>
    /// <param name="CurrentIdentity"></param>
    /// <returns></returns>
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Re validate existing Claims here
        foreach (var Claim in CurrentIdentity.Claims) {
            if (!userIdentity.HasClaim(Claim.Type, Claim.Value))
                userIdentity.AddClaim(new Claim(Claim.Type, Claim.Value));
        }

        return userIdentity;
    }

}

ADDENDUM 2

I had to refine further me mechanism because my previosu ADDENDUM would lead in some peculiar cases to same problem described during re-validation. The key to the current definitive solution is to Add Claims that I can clearly identify and Add only those during re-validation, without having to try to distinguish betweeb native ones (ASP Identity) and mine. So now during LogIn I add the following custom Claims:

 User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CustomClaim.CultureUI", ClaimValue = UserProfile.CultureUI });
 User.Claims.Add(new ApplicationIdentityUserClaim() { ClaimType = "CustomClaim.CompanyId", ClaimValue = model.CompanyId });

Note the Claim Type which now starts with "CustomClaim.".

Then in re-validation I do the following:

  public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, string> manager, ClaimsIdentity CurrentIdentity)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Re validate existing Claims here
        foreach (var Claim in CurrentIdentity.FindAll(i => i.Type.StartsWith("CustomClaim.")))
        {
            userIdentity.AddClaim(new Claim(Claim.Type, Claim.Value));

            // TODO devo testare perché va in loop la pagina Err500 per cui provoco volontariamente la duplicazioen delle Claims
            //userIdentity.AddClaims(CurrentIdentity.Claims);

        }

        return userIdentity;
    }

userIdentity does not contain the Custom Claims, while CurrentIdentity does contain both, but the only one I have to "re attach" to the current Identity are my custom one.

So far it is working fine, so I'll mark this as teh answer.

Hope it helps !

Lorenzo