4

Using ASP.NET Identity 2.1.0,

I'm trying to add a custom Claim so that it gets added to the round-tripping cookie, and not be added to the datastore.

The Claim is for a unique Session Id, unique login, even if for the same UserId (in order to have better auditing of operations done per Session/Client IP address).

The attempt so far is:

            Provider = new CookieAuthenticationProvider
            {
                OnResponseSignIn = (x) =>
                {

                    //Let's pretend this is a Session table Id:
                    var st = x.Identity.FindFirstValue("ST");
                    if (string.IsNullOrEmpty(st))
                    {
                        //Damn! always needs regeneration because not round-tripping coming back :-(
                        //Could use Session, but that defeats the purpose of using a cookie...
                        st = Guid.NewGuid().ToString();
                    }
                    x.Identity.AddClaim(new Claim("ST", st));
                    x.OwinContext.Authentication.SignIn(x.Identity);
                },
                // 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.FromSeconds(6),
                    regenerateIdentity: async (manager, user) =>
                    {

                        var x = await user.GenerateUserIdentityAsync(manager);
                        return x;
                    }
                    )
            }
        });   

using a cache (Session/load balanced Shared/etc.) for the SessionId, using the UserId as the key obviously is not going to work (would return the same SessionId, no matter the ClientIP)

using the UserId + ClientIP as the key would return a SessionId... But ClientIP is notably error prone, so that's a failure waiting to happen.

using a secondary cookie sounds maybe it could work but I'm loath to go creating cookies willy-nilly for a security system without understanding how I would mitigate every single hijacking problem this could bring up....

anybody have a better (hopefully simpler) solution?

What's the class that manages the deserializes the Cookie into an Identity, and back again, and checks whether its still valid? Could I make a custom one, and add a secondary value in there before it is serialized?

Thanks for your help!

stacker
  • 183
  • 11

2 Answers2

0

user.GenerateUserIdentityAsync(manager); is giving your claims principal. That's the perfect place you can add your own claims.

Or you can implement IClaimsIdentityFactory and give it to UserManager.ClaimsIdentityFactory. Better though would be to override provided ClaimsIdentityFactory and add your claims after that

I've blogged about it a while ago, scroll down to "Adding default claims" section.

trailmax
  • 34,305
  • 22
  • 140
  • 234
  • Once again, thanks Max. I can see that overridding ClaimsIdentityFactory is the right place to do this kind of work...But is not the complete solution for the above problem: nothing specific to a single user/*session/cookie* is round tripped -- it's just rebuilding a new cookie from getting a single user's Claims, which are all *shared across all cookies*. I understand I can add more claims as well...but I need some round-tripped info to help me get there. ie. the result at present is still user-allbrowsercookies, not *user-session specific*. Does the above help describe the problem better? – stacker Oct 14 '14 at 21:56
  • @stacker sorry, not exactly sure what you trying to achieve. Are you trying to display all browsers where user is logged in? – trailmax Oct 14 '14 at 22:06
  • I'm after finer grain auditing. Imagine JohnS signing in from CA, and doing some operations. At approx the same time JohnS signs in from some dodgy country, and does a money XFer there. It's obvious that someone else has gotten the pwd. If I log the activities against User.Id it's impossible to distinguish between the 2, which one is error/refundable. Whereas if I log the operation against a Session.Id unique per cookie I can tie the 2nd ops back to a dodgy IP, and refund JohnS's account. Right now, at best, I have two SessionLog entry dates. One from CA, then one from somewhere else... – stacker Oct 15 '14 at 07:04
  • I think you'll have to go with IP and combination of browser properties and probably persist into you DB where and who logged in. Sorry, can't think of anything concrete for your case. Certainly Identity does not have anything like this inbuilt. – trailmax Oct 15 '14 at 08:50
  • Suspect that it's a slippery slope to get right, but I'll give it a shot using a Db Session table and a Second cookie to persist the Session.Id, and post here any advances... Night. Funny...first page I found was...yours ;-) http://tech.trailmax.info/2014/08/aspnet-identity-cookie-format/ – stacker Oct 15 '14 at 10:05
  • You can also look into using Viewmodels for round tripping, I find them to be much easier than the cookie thing for keeping state. – JWP Oct 16 '14 at 11:13
0

I'd like to investigate how to generate the ASP.NET Identity cookie exactly as I would like (containing the Identity+Claims, plus my Session), but until I understand that, this is how I did it using a second cookie:

OnResponseSignIn = (x) =>
{
    string key = "SessionId";
    string serializedSessionId;
    var cookie = x.OwinContext.Request.Cookies.SingleOrDefault(y => y.Key == key);
    if (!string.IsNullOrEmpty(cookie.Value))
    {
        var serializedAndEncryptedText = cookie.Value;
        serializedSessionId = /*decode*/ serializedAndEncryptedText;
        //...maybe update the Session record's last known Activity date?
        //and or check that the value contains the SessionId:UserId, and if UserId has changed,
        //rebuild a new Session (that's in case Sign out fails to destroy all occurances of it)...
    }
    else
    {
        var check = x.OwinContext.Request.Environment;
        serializedSessionId = new SessionService().CreateSession(HttpContext.Current.Request.UserHostAddress).ToString(); //pretend that we hit the db...
        //TODO: how can we encrypt this value so that it's safer than just ClearText?
        string serializedAndEncryptedText = serializedSessionId; 
        cookie = new KeyValuePair<string, string>(key,serializedAndEncryptedText);
        x.Response.Cookies.Append(cookie.Key, cookie.Value);  
    }
    //TODO: what's not good is that we have to remember to destroy the cookie
    //every time we sign out...there's risk we don't catch every single occurance of it
    //(eg: if the underlying Manager is invoked, rather than the ApplicationManager)

    serializedSessionId = cookie.Value;
    x.Identity.AddClaim(new Claim(key, serializedSessionId));
    x.OwinContext.Authentication.SignIn(x.Identity);
},

and then destroyed the cookie when the Login, ExternalLogin, or Logout button was clicked with something similar to:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult LogOff()
    {

        var sessionIdCookie = this.HttpContext.Request.Cookies["SessionId"];
        if (sessionIdCookie != null)
        {
            sessionIdCookie.Value = string.Empty;
            sessionIdCookie.Expires = DateTime.Now.AddYears(-1);
            this.HttpContext.Response.Cookies.Add(sessionIdCookie);
        }
        AuthenticationManager.SignOut();

        return RedirectToAction("Index", "Home");
    }

The sesionservice was no more than the following crappy code that I'll optimised for DI later:

public class SessionService
{
    public Guid CreateSession(string clientIP)
    {
        Session session =new Session();
        session.DateTimeStartedUtc = DateTime.UtcNow;
        session.ClientIP = clientIP;
        ApplicationDbContext applicationDbContext = new ApplicationDbContext();
        applicationDbContext.Set<Session>().Add(session);
        applicationDbContext.SaveChanges();
        return session.Id;
    }   
}

It's all POC code that a lot of cleanup, but for now it will work in the demo MVC app, where Cookies are common. Next, get this to work in an SPA app (but have to understand what/how Bearer tokens work, I think, before I can progress any further).

If anyone sees anything stupid with the above, please let me know...getting this right is really important to us (obviously, being security and all... :-) )

Thanks.

stacker
  • 183
  • 11