How to update a claim in ASP.NET Identity?
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.
Register a
UserClaimsPrincipalFactory
so that every timeSignInManager
sings user in, the claims are created.services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimService>();
Implement a custom
UserClaimsPrincipalFactory<TUser, TRole>
like belowpublic 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; } }
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);
Related videos on Youtube
Irshu
All things politics, economics, tech and software engineering.
Updated on August 21, 2021Comments
-
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 over 5 yearsIf 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 over 9 yearsYou can update the claim but still need to log the user out and in with the updated identity.
-
Irshu over 9 yearsno it doesnt logout the user, we just updating the user's cookie
-
Dennis van der Stelt about 9 yearsRemember 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 over 8 yearsI would use a stored procedure to update the claim table directly.
-
saml about 8 yearsFinally. 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 about 8 yearsWhat 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 almost 8 yearsThe clue here, for me, was to do the
SignInAsync()
after setting up the Claims. -
Martín almost 8 yearsAnybody has the same solution for Asp.Net One Core?
-
Whoever over 7 yearsThis 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 over 7 yearsThis 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 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 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 over 7 yearsvar 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 over 7 years@liuhongbo is right.
GetClaimValue
shouldreturn null
ifclaim == null
when usingFirstOrDefault
. Thanks for this though, it helped me! -
Frank Fu over 7 yearsNicely picked up @liuhongbo and I've made changes accordingly
-
Wrightboy about 7 yearsThanks 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 about 6 yearsI 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 over 5 yearsThanks for the tip on removing the claims from the DB. Made me realize I needed to clean-up after myself.
-
Anh Pham over 5 yearsUsually 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 over 5 yearsThis 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 over 5 yearsLooking 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 over 5 yearsLooking 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 almost 5 yearsThanks, faced the same issue and this solution works better for updating the claims for a signed user.
-
Murphybro2 almost 5 yearsHow do you do this with .net core 2.2? The authentication property is deprecated.
-
darkezm0 over 4 yearsthanks, this worked great for me in asp.net core 3.1!
-
Kevin Tran about 4 yearsThanks! faced the same issue as well in net core 3.1
-
Markus Knappen Johansson over 3 yearsI 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 almost 3 yearsThis works, I finally give up the pieces of IUserStore, SignInManager, UserManager, ClaimsPrincipalAccessor, ClaimsManager ... really complicated things
-
Henrique Belotto almost 3 yearsHow to I get the UserManager first and the user for the first line?
-
JsAndDotNet almost 3 yearsThat would be dependency injected into the controller or class. Maybe something like this post might help.
-
Caner over 2 yearsit is not working for me, I didnt see new user claims in HttpContext.User.Claims after call SignInAsync. do you have an idea?
-
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 over 2 yearsHello, 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 methodAddClaimAsync(T user, Claim claim)
of the user store is not called. Is there something extra I need to do? -
Nileksh Dhimer over 2 yearsFor me, I add just Step-3 lines and it's working fine for me. Thanks @Mahmut C
-
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"));