How to update a claim in ASP.NET Identity?

130,381

Solution 1

I created a Extension method to Add/Update/Read Claims based on a given ClaimsIdentity

namespace Foobar.Common.Extensions
{
    public static class Extensions
    {
        public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value)
        {
            var identity = currentPrincipal.Identity as ClaimsIdentity;
            if (identity == null)
                return;

            // check for existing claim and remove it
            var existingClaim = identity.FindFirst(key);
            if (existingClaim != null)
                identity.RemoveClaim(existingClaim);

            // add new claim
            identity.AddClaim(new Claim(key, value));
            var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
            authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
        }

        public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
        {
            var identity = currentPrincipal.Identity as ClaimsIdentity;
            if (identity == null)
                return null;

            var claim = identity.Claims.FirstOrDefault(c => c.Type == key);
            return claim.Value;
        }
    }
}

and then to use it

using Foobar.Common.Extensions;

namespace Foobar.Web.Main.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            // add/updating claims
            User.AddUpdateClaim("key1", "value1");
            User.AddUpdateClaim("key2", "value2");
            User.AddUpdateClaim("key3", "value3");
        }

        public ActionResult Details()
        {
            // reading a claim
            var key2 = User.GetClaimValue("key2");          
        }
    }
}

Solution 2

You can create a new ClaimsIdentity and then do the claims update with such.

set {
    // get context of the authentication manager
    var authenticationManager = HttpContext.GetOwinContext().Authentication;

    // create a new identity from the old one
    var identity = new ClaimsIdentity(User.Identity);

    // update claim value
    identity.RemoveClaim(identity.FindFirst("AccountNo"));
    identity.AddClaim(new Claim("AccountNo", value));

    // tell the authentication manager to use this new identity
    authenticationManager.AuthenticationResponseGrant = 
        new AuthenticationResponseGrant(
            new ClaimsPrincipal(identity),
            new AuthenticationProperties { IsPersistent = true }
        );
}

Solution 3

Another (async) approach, using Identity's UserManager and SigninManager to reflect the change in the Identity cookie (and to optionally remove claims from db table AspNetUserClaims):

// Get User and a claims-based identity
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var Identity = new ClaimsIdentity(User.Identity);

// Remove existing claim and replace with a new value
await UserManager.RemoveClaimAsync(user.Id, Identity.FindFirst("AccountNo"));
await UserManager.AddClaimAsync(user.Id, new Claim("AccountNo", value));

// Re-Signin User to reflect the change in the Identity cookie
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);

// [optional] remove claims from claims table dbo.AspNetUserClaims, if not needed
var userClaims = UserManager.GetClaims(user.Id);
if (userClaims.Any())
{
  foreach (var item in userClaims)
  {
    UserManager.RemoveClaim(user.Id, item);
  }
}

Solution 4

Using latest Asp.Net Identity with .net core 2.1, I'm being able to update user claims with the following logic.

  1. Register a UserClaimsPrincipalFactory so that every time SignInManager sings user in, the claims are created.

    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimService>();
    
  2. Implement a custom UserClaimsPrincipalFactory<TUser, TRole> like below

    public class UserClaimService : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole>
    {
        private readonly ApplicationDbContext _dbContext;
    
        public UserClaimService(ApplicationDbContext dbContext, UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
        {
            _dbContext = dbContext;
        }
    
        public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
        {
            var principal = await base.CreateAsync(user);
    
            // Get user claims from DB using dbContext
    
            // Add claims
            ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("claimType", "some important claim value"));
    
            return principal;
        }
    }
    
  3. Later in your application when you change something in the DB and would like to reflect this to your authenticated and signed in user, following lines achieves this:

    var user = await _userManager.GetUserAsync(User);
    await _signInManager.RefreshSignInAsync(user);
    

This makes sure user can see up to date information without requiring login again. I put this just before returning the result in the controller so that when the operation finishes, everything securely refreshed.

Instead of editing existing claims and creating race conditions for secure cookie etc, you just sign user in silently and refresh the state :)

Solution 5

I get that exception too and cleared things up like this

var identity = User.Identity as ClaimsIdentity;
var newIdentity = new ClaimsIdentity(identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
newIdentity.AddClaims(identity.Claims.Where(c => false == (c.Type == claim.Type && c.Value == claim.Value)));
// the claim has been removed, you can add it with a new value now if desired
AuthenticationManager.SignOut(identity.AuthenticationType);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, newIdentity);
Share:
130,381

Related videos on Youtube

Irshu
Author by

Irshu

All things politics, economics, tech and software engineering.

Updated on August 21, 2021

Comments

  • Irshu
    Irshu over 2 years

    I'm using OWIN authentication for my MVC5 project. This is my SignInAsync

     private async Task SignInAsync(ApplicationUser user, bool isPersistent)
            {
                var AccountNo = "101";
                AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
                var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
                identity.AddClaim(new Claim(ClaimTypes.UserData, AccountNo));
                AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, RedirectUri="Account/Index"}, identity);
            }
    

    As you can see, i added AccountNo into the Claims list.

    Now, how can I update this Claim at some point in my application? So far, i have this:

     public string AccountNo
            {
    
                get
                {
                    var CP = ClaimsPrincipal.Current.Identities.First();
                    var Account= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData);
                    return Account.Value;
                }
                set
                {
                    var CP = ClaimsPrincipal.Current.Identities.First();
                    var AccountNo= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData).Value;
                    CP.RemoveClaim(new Claim(ClaimTypes.UserData,AccountNo));
                    CP.AddClaim(new Claim(ClaimTypes.UserData, value));
                }
    
            }
    

    when i try to remove the claim, I get this exception:

    The Claim 'http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata: 101' was not able to be removed. It is either not part of this Identity or it is a claim that is owned by the Principal that contains this Identity. For example, the Principal will own the claim when creating a GenericPrincipal with roles. The roles will be exposed through the Identity that is passed in the constructor, but not actually owned by the Identity. Similar logic exists for a RolePrincipal.

    How does one remove and update the Claim?

    • Hooman Bahreini
      Hooman Bahreini over 5 years
      If you are storing User Info in a Claim and you want to update the Claim, once User Info has changed, you can call: SignInManager.SignInAsync to refresh the value of the Claim. See this question
  • user3210546
    user3210546 over 9 years
    You can update the claim but still need to log the user out and in with the updated identity.
  • Irshu
    Irshu over 9 years
    no it doesnt logout the user, we just updating the user's cookie
  • Dennis van der Stelt
    Dennis van der Stelt about 9 years
    Remember that this updates identity only. If you want to store these claims and load them automatically upon request, you need the usermanager to remove and update them as well. Took me some time! :(
  • hidden
    hidden over 8 years
    I would use a stored procedure to update the claim table directly.
  • saml
    saml about 8 years
    Finally. I had another solution to this and it seemed to be working... mostly. But ultimately switched to this method since it seems to work always. Thank you!
  • Nozim Turakulov
    Nozim Turakulov about 8 years
    What if I don't have cookies at all and use only accessToken? In my case claims are the same on the next request as before change. The only way to update claims I have is to logout user and ask him to login once again :-(
  • FirstVertex
    FirstVertex almost 8 years
    The clue here, for me, was to do the SignInAsync() after setting up the Claims.
  • Martín
    Martín almost 8 years
    Anybody has the same solution for Asp.Net One Core?
  • Whoever
    Whoever over 7 years
    This appears only working for the current application. I would like to update the cookie issued by SSO server so other apps can access them too. Any idea how? Thanks
  • Whoever
    Whoever over 7 years
    This appears only working for the current application. I would like to update the cookie issued by SSO server so other apps can access them too. Any idea how? Thanks
  • Mog0
    Mog0 over 7 years
    @Whoever As the cookie is signed by the SSO server to show that it hasn't been tampered with (and thus can be trusted), I would be surprised if there was any way to achieve this as it would be tampering.
  • Whoever
    Whoever over 7 years
    @Mog0 There's an illusion that this might work when applications are on the same domain, like during local debug. So you are right, it cain't be done. stackoverflow.com/questions/39277702/…
  • liuhongbo
    liuhongbo over 7 years
    var claim = identity.Claims.First(c => c.Type == key); return claim.Value;should be var claim = identity.Claims.FirstOrDefault(c => c.Type == key); return claim?.Value;
  • EarlCrapstone
    EarlCrapstone over 7 years
    @liuhongbo is right. GetClaimValue should return null if claim == null when using FirstOrDefault. Thanks for this though, it helped me!
  • Frank Fu
    Frank Fu over 7 years
    Nicely picked up @liuhongbo and I've made changes accordingly
  • Wrightboy
    Wrightboy about 7 years
    Thanks for this. Though not sure why you use the FindFirst method in AddOrUpdate(), but then manually compare when in GetClaimValue(). Really GetClaimValue() could just consist of return (currentPrincipal.Identity as ClaimsIdentity)?.FindFirst(key)?.Value;. And can be even cleaner if you write it as an expression-bodied function.
  • Ananda G
    Ananda G about 6 years
    I have replaced one claim value in MVC5 WEB API, it's replacing well. But when I am going to another action then it role backs to previous situation with the previous value. Why such happening?
  • Uber Schnoz
    Uber Schnoz over 5 years
    Thanks for the tip on removing the claims from the DB. Made me realize I needed to clean-up after myself.
  • Anh Pham
    Anh Pham over 5 years
    Usually it's better to explain a solution instead of just posting some rows of anonymous code. You can read How do I write a good answer, and also Explaining entirely code-based answers
  • Mahmut C
    Mahmut C over 5 years
    This is due to not updating the cookies authenticated. This answer only updates the Identity which is at controller level. To get other parts of the app getting the updates, you need to refresh the sign in and renew the cookies. See my answer below: stackoverflow.com/a/53110508/1051256
  • luizs81
    luizs81 over 5 years
    Looking at the constructor of AuthenticationResponseGrant, it also accepts a instance of ClaimsIdentity instead of ClaimsPrincipal. Any difference in using that? Not a big difference, but would save a cast.
  • luizs81
    luizs81 over 5 years
    Looking at the constructor of AuthenticationResponseGrant, it also accepts a instance of ClaimsIdentity instead of ClaimsPrincipal. Any difference in using that? Not a big difference, but would save creating a new object.
  • ameya
    ameya almost 5 years
    Thanks, faced the same issue and this solution works better for updating the claims for a signed user.
  • Murphybro2
    Murphybro2 almost 5 years
    How do you do this with .net core 2.2? The authentication property is deprecated.
  • darkezm0
    darkezm0 over 4 years
    thanks, this worked great for me in asp.net core 3.1!
  • Kevin Tran
    Kevin Tran about 4 years
    Thanks! faced the same issue as well in net core 3.1
  • Markus Knappen Johansson
    Markus Knappen Johansson over 3 years
    I had a problem that the claims for the current request was not updated, but found this: github.com/brockallen/BrockAllen.MembershipReboot/issues/51 so that the current request gets updates.
  • Phoeson
    Phoeson almost 3 years
    This works, I finally give up the pieces of IUserStore, SignInManager, UserManager, ClaimsPrincipalAccessor, ClaimsManager ... really complicated things
  • Henrique Belotto
    Henrique Belotto almost 3 years
    How to I get the UserManager first and the user for the first line?
  • JsAndDotNet
    JsAndDotNet almost 3 years
    That would be dependency injected into the controller or class. Maybe something like this post might help.
  • Caner
    Caner over 2 years
    it is not working for me, I didnt see new user claims in HttpContext.User.Claims after call SignInAsync. do you have an idea?
  • Vitkir
    Vitkir over 2 years
    @Caner As it was with me. There are claims based on user data in the database. I updated them and then re-registered the user so that the app updates the claims on its own. You can also update claims manually, but then you have to update cookies, etc. also manually, but with this I have not figured out how to do it, and I can’t tell you. P.s. sorry for google translate)
  • jstuardo
    jstuardo over 2 years
    Hello, I have a custom user store, so that I implemented IUserClaimStore in it. when I call identity.AddClaim(new Claim(key, value)); from your code, the method AddClaimAsync(T user, Claim claim) of the user store is not called. Is there something extra I need to do?
  • Nileksh Dhimer
    Nileksh Dhimer over 2 years
    For me, I add just Step-3 lines and it's working fine for me. Thanks @Mahmut C
  • Robert Shattock
    Robert Shattock almost 2 years
    identity.RemoveClaim(identity.FindFirst("AccountNo")); will result in an InvalidOperationException if there is no claim of type "AccountNo" - in this situation there probably always is that claim, but in other situations you do it a bit more defensively with TryRemoveClaim e.g. identity.TryRemoveClaim(identity.FindFirst("AccountNo"));