Asp.Net Identity with 2FA - remember browser cookie not retained after session

asp.net, asp.net-identity, cookies, session

Solution

It doesn't seem like this code was designed to set more than one identity cookie in the same request/response because the OWIN cookie handlers end up sharing the same AuthenticationProperties. This is because the AuthenticationResponseGrant has a single principal, but the principal can have multiple identities.

You can workaround this bug by altering and then restoring the AuthenticationProperties in the ResponseSignIn and ResponseSignedIn events specific to the 2FA cookie provider:

        //Don't use this.
        //app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

        //Set the 2FA cookie expiration and persistence directly
        //ExpireTimeSpan and SlidingExpiration should match the Asp.Net Identity cookie setting
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
            AuthenticationMode = AuthenticationMode.Passive,
            CookieName = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
            ExpireTimeSpan = TimeSpan.FromHours(2),
            SlidingExpiration = true,
            Provider = new CookieAuthenticationProvider
            {
                OnResponseSignIn = ctx =>
                {
                    ctx.OwinContext.Set("auth-prop-expires", ctx.Properties.ExpiresUtc);
                    ctx.OwinContext.Set("auth-prop-persist", ctx.Properties.IsPersistent);
                    var issued = ctx.Properties.IssuedUtc ?? DateTimeOffset.UtcNow;
                    ctx.Properties.ExpiresUtc = issued.AddDays(14);
                    ctx.Properties.IsPersistent = true;
                },
                OnResponseSignedIn = ctx =>
                {
                    ctx.Properties.ExpiresUtc = ctx.OwinContext.Get<DateTimeOffset?>("auth-prop-expires");
                    ctx.Properties.IsPersistent = ctx.OwinContext.Get<bool>("auth-prop-persist");
                }
            }
        });

Make sure to set the same ExpireTimeSpan and SldingExpiration as your main Asp.Net Identity cookie to preserve those settings (since they get merged in the AuthenticationResponseGrant).

Problem

I'm using the latest sample code for MVC5.2 with Asp.Identity and Two Factor authentication. With 2FA enabled, when a user logins, the get prompted for a code (sent by phone or email) and they have the option to "Remember Browser" - so that they don't get ask for codes again on that browser. This is handled in the VerifyCode action ``` var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser); ``` Note that `model.RememberMe` is not used in the default templates so it is false. I find when I do this the `.AspNet.TwoFactorRememberBrowser` that gets set, expires on session end (so it does not remember the browser) Now if I set `isPersistent = true`, `.AspNet.TwoFactorRememberBrowser` gets an expiration of 30 days which is great, but the `.AspNet.ApplicationCookie` also gets a 30 day expiration - which means that when I close the browser and re-open, I am automatically logged in. I want it so that it doesn't persist my login, but that it will persist my choice of remembering the 2FA code. Ie the user should always have to login, but they should not be asked for a 2fa code if they have already save it. Has anybody else seen this, or am I missing something?

Original source