Persisting claims across requests

bckpwrld picture bckpwrld · Aug 13, 2014 · Viewed 12.1k times · Source
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

Answer

trailmax picture trailmax · Aug 14, 2014

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.