1

I need to write a custom cookie to the client after the user has logged in. This project is using Asp.Net Identity 2.0 and the default Visual Studio MVC 5 template so its no longer straight forward as it had been in the past.

I thought the place to do this would be inside the ApplicationUser.GenerateUserIdentityAsync() method, but HttpContext.Current is always null when this method executes and I assume its because its declared as an asynchronous Task<> and being called from a separate thread.

I also tried creating an event in the ApplicationUser class but this doesn't work either because again, its called from a separate thread. I could re-write this method to be synchronous, but I'm wondering what the correct way to do this using the out of the box template that Microsoft is providing.

public class ApplicationUser : IdentityUser<int, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
    public ApplicationUser()
    {
        LastLogin = String.Empty;
        HasLoggedInBefore = false;

        UserCreated += ApplicationUser_UserCreated;
    }

    void ApplicationUser_UserCreated(object sender, ApplicationUser user)
    {
        // write our custom cookie
        user.WriteDetailsCookie();
    }

    public event EventHandler<ApplicationUser> UserCreated;
    protected virtual void OnUserCreated(ApplicationUser user)
    {
        EventHandler<ApplicationUser> handler = UserCreated;
        if (handler != null)
        {
            handler(this, user);
        }
    }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        OnUserCreated(this); // fire our event
        return userIdentity;
    }

    public void WriteDetailsCookie()
    {
        var lastLoginCookie = new HttpCookie("UserDetails");
        lastLoginCookie.Values.Add("userName", UserName);
        lastLoginCookie.Values.Add("lastLogin", DateTime.UtcNow.ToString("G"));
        lastLoginCookie.Expires = DateTime.Now.AddDays(90d);
        HttpContext.Current.Response.Cookies.Add(lastLoginCookie);
    }
}
TugboatCaptain
  • 4,150
  • 3
  • 47
  • 79

2 Answers2

1

Got this working today. The correct out-of-box location to do something after the user has logged in and has an Identity created is in the Startup.Auth.cs file in the CookieAuthenticationProvider.OnResponseSignIn delegate.

    public void ConfigureAuth(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
                    getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))),
                OnResponseSignIn = cookieSignInCtx =>
                {
                    // do your post identity creation stuff here
                    var userManager = cookieSignInCtx.OwinContext.GetUserManager<ApplicationUserManager>();
                    var user = userManager.FindById(cookieSignInCtx.Identity.GetUserId<int>());
                    if (user != null)
                        user.WriteDetailsCookie(HttpContext.Current); // HttpContext.Current is not null, yeah!
                }
            }
        });
}

I went down a lot of rabbit holes learning about TAP and why HttpContext may be null when tasks are executed but these are all dead ends in this context. I say this next statement without being 100% sure, but I believe that most of Asp.Net Identity 2.0 API asynchronous methods are completely divorced from the Asp.Net pipeline and have no access to anything other than what is passed into them as arguments due to the OWIN middleware.

Setting aspnet:UseTaskFriendlySynchronizationContext to true has no impact.

Community
  • 1
  • 1
TugboatCaptain
  • 4,150
  • 3
  • 47
  • 79
0

Why do you need a custom cookie there?

OWIN cookie is encrypted and messing with it's contents will only invalidate it. I did write a blog post about the cookie format. And anything you do to it will probably break the OWIN pipeline.

If you need to add extra data in the cookie you can add claims onto created identity object in GenerateUserIdentityAsync and pass that identity over to OWIN.
Later in the further requests, this data will be available in ClaimsPrincipal.Current.Claims and also it will be properly stored in cookie according to OWIN algorithms.

trailmax
  • 34,305
  • 22
  • 140
  • 234