ASP.NET Identity 2 Session

Oct 12, 2014 at 11:28 PM
Hi,
We're looking at ASP.NET Identity 2.x to determine if it is suitable for an upcoming project.

There are a set of requirements we have to comply with, that would be too big a post, so I'll try to break them down to small parts.

Session centric versus User centric... the library uses client side cookies to manage individual Cookies.

The requirements are to be able to audit individual sessions: in the case that a legitimate user signs in in California, but at the same time someone is using that person's username/password and signed in New Zealand, it will be important later to distinguish which action was done legitimately, and which one can be proved to be fraudulently/be refunded.

For that, I would need to get control of how the cookie is generated.

From what I've seen, OnResponseSignIn/OnValidateIdentity are about converting Identity to cookie, and cookie to Identity.

It's not low enough (I don't want to serialize an whole Identity, even with only one Claim, only a Session record Guid), but I'll take what I can get until I see a better way.

In this direction, I've seen a post (http://stackoverflow.com/questions/19192428/server-side-claims-caching-with-owin-authentication) suggesting that the best place to take ownership of the cookies contents is within the CookieAuthenticationProvider's OnResponseSignIn handler -- but I didn't get as much luck.

For one, the moment I replaced the Identity, other parts of the framework started demanding I put back in Claims so that they could continue to work.

Q: Our requirement was that the Cookie contain only a Session record Guid, nothing else. I'll need to find some documentation as to what OWIN requires to work if I have to justify why we can't do it as spec'ed.

For another, it appears that something is still missing: even if I replace (and rebuild the necessary claims) the identity within OnResponseSignIn, and add a SessionToken claim there, the identity that shows up later within OnValidateIdentity never has the SesionToken claim added... Not sure why the claim I added before it got serialized was not good enough to round-trip as part of the cookie.

My attempt so far (sorry for the mess due to flailing around throughout the eve) is as follows...
Is it going in the right direction, and if so, what's left to do before it works? If not...what's a better approach?

Thanks very much!
        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        { 
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {

                OnResponseSignIn = (x) =>
                                       {
                                           //TODO: Pretend we have built a Sesion record and got back its Id:
                                           Guid sessionToken = Guid.NewGuid();
                                           x.Identity.AddClaim(new Claim("SessionToken", sessionToken.ToString()));
                                           System.Web.HttpContext.Current.Cache[sessionToken.ToString()] = x.Identity.Claims.ToList();

                                           var cookiesSoFar = System.Web.HttpContext.Current.Response.Cookies;


                                           //The Authentication Type must match:
                                           var tmp = x.AuthenticationType;

                                           ClaimsIdentity claimsIdentity = new ClaimsIdentity(null, tmp);

                                           //REQUIRED: or you get an Error from @Html.AntiForgergyToken(): "A claim of type 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' or 'http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider' was not present on the provided ClaimsIdentity. To enable anti-forgery token support with claims-based authentication, please verify that the configured claims provider is providing both of these claims on the ClaimsIdentity instances it generates. If the configured claims provider instead uses a different claim type as a unique identifier, it can be configured by setting the static property AntiForgeryConfig.UniqueClaimTypeIdentifier."
                                           tmp = x.Identity.FindFirstValue("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider");
                                           claimsIdentity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", tmp));

                                           //REQUIRED: or you get an Error from @Html.AntiForgeryToken():
                                           tmp = x.Identity.FindFirstValue(ClaimTypes.NameIdentifier);
                                           claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tmp));

                                           //REQUIRED: or it won't be able to find itself on the other end:
                                           claimsIdentity.AddClaim(new Claim("SessionToken", sessionToken.ToString()));

                                           //REQUIRED: or OnValidateIdentity is not invoked:
                                           tmp = x.Identity.FindFirstValue("AspNet.Identity.SecurityStamp");
                                           claimsIdentity.AddClaim(new Claim("AspNet.Identity.SecurityStamp", tmp));

                                           //var identity = x.Identity;
                                           claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, "N:" + sessionToken.ToString()));

                                           var roles = x.Identity.Claims.Where(y => y.Type == x.Identity.RoleClaimType).ToArray();

                                           //Cache...
                                           //x.Identity = claimsIdentity;


                                           var ctx = x.OwinContext;
                                           var authenticationManager = ctx.Authentication;
                                           //authenticationManager.SignIn( x.Identity.FindFirstValue(ClaimTypes.NameIdentifier));
                                       },
                // A delegate assigned to this property will be invoked when the related method is called
                // 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(0),
                                                     regenerateIdentity: 
                                                         (manager, user) =>  
                                                             {
                                                                 Task<ClaimsIdentity> identity =  user.GenerateUserIdentityAsync(manager);
                                                                 ReattachRoles(identity.Result);
                                                                 return identity;
                                                             }
                                                 )
                // A delegate assigned to this property will be invoked when the related method is called
                //OnApplyRedirect 
            }
        });

    static void ReattachRoles(ClaimsIdentity claimsIdentity)
    {

                                                                 string stString = claimsIdentity.FindFirstValue("SessionToken");
        if (string.IsNullOrEmpty(stString))
        {
            return;
        }
        List<Claim> cachedClaims = (List<Claim>)System.Web.HttpContext.Current.Cache[stString];


        var tmp = claimsIdentity.FindFirst(x => x.Type == ClaimTypes.Name);
        string newTmpValue = "***:" + cachedClaims.Count + ":" + tmp.Value;
        cachedClaims.Remove(tmp);
        cachedClaims.Add(new Claim(tmp.Type, newTmpValue));

        foreach (var newClaim in cachedClaims)
        {
            foreach (var c in claimsIdentity.FindAll(x => x.Type == newClaim.Type).ToArray())
            {
                claimsIdentity.RemoveClaim(c);
            }

            claimsIdentity.AddClaim(newClaim);