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);
}
}
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.