Persisting claims across requests
Asked Answered
G

2

13
var user = UserManager.Find(...);

ClaimsIdentity identity = UserManager.CreateIdentity(
          user, DefaultAuthenticationTypes.ApplicationCookie );


var claim1 = new Claim(
          ClaimType = ClaimTypes.Country, ClaimValue = "Arctica", UserId = user.Id );
identity.AddClaim(claim1);

AuthenticationManager.SignIn(
          new AuthenticationProperties { IsPersistent = true }, identity );

var claim2 = new Claim(
          ClaimType = ClaimTypes.Country, ClaimValue = "Antartica", UserId = user.Id );
identity.AddClaim(claim2);

Both claim1 and claim2 are persisted across requests only for the time ClaimsIdentity user is logged in. In other words, when user logs out by calling SignOut(), the two claims are also removed and as such the next time this user logs in, it is no longer a member of these two claims ( I assume the two claims don't exist anymore )

The fact that claim2 is persisted across requests ( even though authentication cookie was already created when claim2 was added to the user ) suggests that claims don't get persisted across requests via authentication cookie, but via some other means.

So how are claims persisted across requests?

EDIT:

1) As far as I can tell, claims of type IdentityUserClaim are never persisted in a cookie?

var user = UserManager.Find(...);

/* claim1 won't get persisted in a cookie */
var claim1 = new IdentityUserClaim
      { ClaimType = ClaimTypes.Country, ClaimValue = "Arctica", UserId = user.Id };
user.Claims.Add(claim1);


ClaimsIdentity identity = UserManager.CreateIdentity(
      user, DefaultAuthenticationTypes.ApplicationCookie );


AuthenticationManager.SignIn(
      new AuthenticationProperties { IsPersistent = true }, identity );

If my assumption is correct, is the reason why IdentityUserClaim instances aren't persisted in a cookie because it is assumed that these claims should be stored in a DB and as such could in subsequent requests be retrieved from a DB, while claims of type Claim usually aren't stored in a DB and hence why they need to be persisted in a cookie?

2)

If you'd like to have a deeper look how it all works, check out the source code of Katana Project

I thought Asp.net Identity 2 was not part of the Katana project ( namely, I've seen people asking when will Microsoft release the source code for Asp.Net Identity, even though Katana source code is already available )?!

thank you

Gustavogustavus answered 13/8, 2014 at 17:6 Comment(2)
Since I haven't done this I won't show it as an answer, but it is my understanding that claims are generally not persisted, but instead are added in memory just as you show here, added when the user logs in. As for the cookie, wouldn't that be created for the user at the end of the request after everything the code has done to it is complete, included claim2?Kilderkin
Claims are persisted in cookie and if created with UserManager stored in database (and retrieved later on SignIn). For the second part of your comment, you are correct there - cookie is not set until the end of the request. And claim1 along with claim2 are sitting in memory until time comes to set the headers on the response.Badger
B
13

Good question. Even made me do a little experiment.

This line:

AuthenticationManager.SignIn(
          new AuthenticationProperties { IsPersistent = true }, identity );

Does not set a cookie. Only sets Identity object for the later callback.

Cookie is only set when the control is passed to middleware and some OWIN internal method called Response.OnSendingHeaders.

So your code is just adding claim2 on the identity object that is stored in memory for later user. In theory you can even set claim1 after you have done the AuthenticationManager.SignIn. And it will be persisted in the cookie anyway.

If you try to add a cliam like this in a controller:

    public ActionResult AddNonPersistedClaim()
    {
        var identity = (ClaimsIdentity)ClaimsPrincipal.Current.Identity;
        identity.AddClaim(new Claim("Hello", "World"));

        return RedirectToAction("SomeAction");
    }

This claim won't be set in the cookie and you will not see it in the next request.

If you'd like to have a deeper look how it all works, check out the source code of Katana Project, look on Microsoft.Owin.Security and Microsoft.Owin.Security.Cookies projects. Along with AuthenticationManager in Microsoft.Owin.Net45 project.

Update

To answer your Edit 1 - IdentityUserClaim is indeed persisted into the database and this is the way you can assign persisted claims to the user. You add these on the user through UserManager

await userManager.AddClaimAsync(userId, new Claim("ClaimType", "ClaimValue"));

This creates records in your database table that represents IdentityUserClaim. When next time user is logged in, these claims are read from the database and added to the identity and are available on ClaimsIdentity.Current via property .Claims or by method .HasClaim().

IdentityUserClaim does not do anything else - just way to serialise Claim object into the database. You don't usually access these directly, unless you want to go "bare knuckles" and write to that table yourself, outside of UserManager.

To put it another way - Identity does not set the cookie. OWIN creates the cookie. Have a look on this piece of code:

    public async Task SignInAsync(IAuthenticationManager authenticationManager, ApplicationUser applicationUser, bool isPersistent)
    {
        authenticationManager.SignOut(
            DefaultAuthenticationTypes.ExternalCookie,
            DefaultAuthenticationTypes.ApplicationCookie,
            DefaultAuthenticationTypes.TwoFactorCookie,
            DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
            DefaultAuthenticationTypes.ExternalBearer);

        var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
        identity.AddClaim(new Claim(ClaimTypes.Email, applicationUser.Email));

        authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
    }

Here Authentication manager is part of OWIN. Identity is part of System.Security.Claims. All that belongs to Identity project is CreateIdentityAsync method - that is basically converts user from the database into ClaimsIdentity with all the persisted roles and claims.

To answer your Edit 2: You are correct, AspNet Identity is not part of Katana project, but Identity uses OWIN (part of Katana) for cookie handling and authorisation. Identity project mostly deals with user/roles/claims persistence and user management, like locking-out, user creation, sending emails with password resetting, 2FA, etc.

What was a surprise for me is that ClaimsPrincipal along with ClaimsIdentity and Claim are part of .Net framework that is available outside of OWIN or Identity. These are used not only in Asp.Net, but in Windows applications. Good thing that .Net now has open-source and you can browse through all these - gives you a better understanding how it all works together. Also if you are doing unit-testing, it is invaluable to know the internals, so you can stub-out all the functionality without using mocks.

Badger answered 14/8, 2014 at 15:27 Comment(8)
Hi, great answer. In case you find the time, could you see the edit I've made? You did answer my original question, so I will accept your answer tomorrow, regardless if you also answer my follow up questionsGustavogustavus
Please see the update. I've done a lot of experimenting with Identity and have found a few intricate details. If you are interested to poke about further, my experiments are available on github.com/trailmax/ClaimsAuthorisationBadger
much thanx for your help. Will check your experimentsGustavogustavus
@Badger so everything you do in the CreateIdentityAsync resp. ClaimsFactory.CreateAsync basically is persisted over requests?Harker
@Harker claims you put on ClaimsIdentity before user sign-in are persisted in the cookie. If you put claims on Identity without sign-in/Sign-out, these claims are lost.Badger
@Badger so there is no possibility without db to persist the claims if you want to change them without signin/signout?Harker
No, you can't change the claims in the cookie without sign-in/sign-out. But you can add claims to user record and these will be available in the cookie on the next sign-in.Badger
Can you even do it w/ signin/signout? Because I'm not even able to do it. I don't store the claims in the db, cookie only. I feel like my principalFactory just empty out my claims on every request.Thanks
M
0

If you are using AD authentication and asp core 2.1 or 2.2, there is an option OpenIdConnectOptions when we configure services named ClaimActions, with the help of ClaimActions you can write class [CustomClaimsFactory] which inherits ClaimActions also override its Run method which actually will set the persistent claims, please find code below :

/* startup.cs */  services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
        {

            options.ClaimActions.Add(new CustomClaimsFactory(
                                    "userName",
                                    "[email protected]"
                                )); 
         }

/*CustomClaimsFactory run method*/ public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
    {
        identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));

    }
Minnie answered 23/2, 2020 at 8:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.