ASP.NET Identity (Part 2)

Home / ASP.NET Identity (Part 2)

Ok, when you title a blog post as Part 1, you better follow up with a Part 2. I hadn’t forgotten, but it’s taken a little to come back around to my adventures with ASP.NET Identity.

Where was I? Oh yeah, last time I discussed how to, mostly, get ASP.NET Identity working with a pre-existing security model. This was working fine for me with the v2.1 Microsoft.AspNet.Identity.EntityFramework packages, but when I updated to the v2.2 packages, things broke.

For whatever reason, the Microsoft.AspNet.Identity.EntityFramework v2.2 packages changed the flow. The v2.1 packages, with my modest code modifications, didn’t seem to go back to the local (EF) database to update/create the current user. But, it may have been performing a check for user existence and then doing a upsert. The v2.2 packages, though, broke this paradigm.


Initially, I simply kept getting an exception after letting ASP.NET Identity know that the user’s credentials were valid. The stack trace wasn’t a lot of help, but I could tell that it had something to do with the EF UserStore. I eventually had to put an EF DbInterceptor in place to see what in the hey-hey was going on. The bottom line was that Identity.EntityFramework’s implementation of IUserStore was trying to perform an UPDATE right after validation. Obviously, though, my user/login did not exist.

This really bugged me since I really didn’t need Identity.EntityFramework at all.  I didn’t even need a UserStore.  I wasn’t storing anything, per se.  To complete the process that had come back around, I decided to completely remove the Identity.Framework packages and see what would break.

The base MVC project template was heavily entrenched in EF goodness.  After removing the nuget references to the Identity.EntityFramework, I found that all of the major components such as the implemented UserStore, and even ApplicationUser, no longer worked – it was all part of Microsoft.AspNet.Identity.EntityFramework. That’s the Microsoft way, I suppose – in for a penny, in for a pound.

Needless to say, pulling the plug kind of turned into a big mess. The coupling to Microsoft.AspNet.Identity.EntityFramework.UserStore was very tight. However, I wanted to get it working on my own, so through much trial and error, I found that there were (6) interfaces that my UserStore had to implement to, at the very least prevent my application from bombing while allowing the user to authenticate.

To start, here’s what my ApplicationUser now looks like implementing, solely, IUser and leaving in place the default Microsoft Task that generates the ClaimsIdentity:

    public class ApplicationUser : IUser
    {
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        {
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        }

        public string Id { get; set; }
        public string UserName { get; set; }
        public string Email { get; set;}
        public string PasswordHash { get; set; }
        public string PhoneNumber { get; set; }
        public string SecurityStamp { get; set; }
    }

This looks like pretty cut and dry boiler plate. Once we start digging into IUserStore, though, things get messy. But, that’s what I started with – my CustomUserStore : IUserStore.

IUserStore only has a few methods and they are relatively descriptive. Initially, I was thinking “hey, this reminds me of creating a custom Membership provider – how hard can it be?” Like I said, there are only a handful of methods, and most of them, I really didn’t even care about:

public System.Threading.Tasks.Task CreateAsync(T user)
{
    throw new NotImplementedException();
}

public System.Threading.Tasks.Task DeleteAsync(T user)
{
    throw new NotImplementedException();
}

public System.Threading.Tasks.Task<T> FindByIdAsync(string userId)
{
    throw new NotImplementedException();
}

public System.Threading.Tasks.Task<T> FindByNameAsync(string userName)
{
    throw new NotImplementedException();
}

public System.Threading.Tasks.Task UpdateAsync(T user)
{
    return Task.FromResult(0);
}

public void Dispose()
{
}

Most methods, for my purposes were a nop. I happily started hitting F5, but kept getting errors about additional unimplemented interfaces on my CustomUserStore. Long story short, I wound up having to implement these interfaces, which I discovered with my sleuthy use of run-time errors: IUserStore, IUserLockoutStore<T, string>, IUserTwoFactorStore<T, string>, IUserEmailStore, IUserPhoneNumberStore, IUserLoginStore.
The final code looks like this:

public class CustomUserStore<T> : IUserStore<T>,
        IUserLockoutStore<T, string>,
        IUserTwoFactorStore<T, string>,
        IUserEmailStore<T>,
        IUserPhoneNumberStore<T>,
        IUserLoginStore<T>
        where T : ApplicationUser
    {
        public System.Threading.Tasks.Task CreateAsync(T user)
        {
            throw new NotImplementedException();
        }

        public System.Threading.Tasks.Task DeleteAsync(T user)
        {
            throw new NotImplementedException();
        }

        public System.Threading.Tasks.Task<T> FindByIdAsync(string userId)
        {
            throw new NotImplementedException();
        }

        public System.Threading.Tasks.Task<T> FindByNameAsync(string userName)
        {
            throw new NotImplementedException();
        }

        public System.Threading.Tasks.Task UpdateAsync(T user)
        {
            return Task.FromResult(0);
        }

        public void Dispose()
        {
        }

        public System.Threading.Tasks.Task<int> GetAccessFailedCountAsync(T user)
        {
            return Task.FromResult(0);
        }

        public System.Threading.Tasks.Task<bool> GetLockoutEnabledAsync(T user)
        {
            return Task.FromResult(false);
        }

        public System.Threading.Tasks.Task<DateTimeOffset> GetLockoutEndDateAsync(T user)
        {
            return Task.FromResult(new DateTimeOffset(DateTime.Now));
        }

        public System.Threading.Tasks.Task<int> IncrementAccessFailedCountAsync(T user)
        {
            return Task.FromResult(0);
        }

        public System.Threading.Tasks.Task ResetAccessFailedCountAsync(T user)
        {
            return Task.FromResult(0);
        }

        public System.Threading.Tasks.Task SetLockoutEnabledAsync(T user, bool enabled)
        {
            return Task.FromResult(0);
        }

        public System.Threading.Tasks.Task SetLockoutEndDateAsync(T user, DateTimeOffset lockoutEnd)
        {
            return Task.FromResult(0);
        }

        public Task<T> FindByEmailAsync(string email)
        {
            throw new NotImplementedException();
        }

        public Task<string> GetEmailAsync(T user)
        {
            return Task<string>.FromResult(user.Email);
        }

        public Task<bool> GetEmailConfirmedAsync(T user)
        {
            throw new NotImplementedException();
        }

        public Task SetEmailAsync(T user, string email)
        {
            throw new NotImplementedException();
        }

        public Task SetEmailConfirmedAsync(T user, bool confirmed)
        {
            throw new NotImplementedException();
        }

        public Task<bool> GetTwoFactorEnabledAsync(T user)
        {
            return Task.FromResult(false);
        }

        public Task SetTwoFactorEnabledAsync(T user, bool enabled)
        {
            throw new NotImplementedException();
        }

        public Task<string> GetPhoneNumberAsync(T user)
        {
            return Task<string>.FromResult(user.PhoneNumber);
        }

        public Task<bool> GetPhoneNumberConfirmedAsync(T user)
        {
            return Task<bool>.FromResult(true);
        }

        public Task SetPhoneNumberAsync(T user, string phoneNumber)
        {
            user.PhoneNumber = phoneNumber;
            return Task.FromResult(true);
        }

        public Task SetPhoneNumberConfirmedAsync(T user, bool confirmed)
        {
            return Task.FromResult(true);
        }

        public Task AddLoginAsync(T user, UserLoginInfo login)
        {
            return Task.FromResult(true);
        }

        public Task<T> FindAsync(UserLoginInfo login)
        {
            throw new NotImplementedException();
        }

        public Task<IList<UserLoginInfo>> GetLoginsAsync(T user)
        {
            var loginInfos = new List<UserLoginInfo>();
            loginInfos.Add(new UserLoginInfo("Local", user.Id));
            return Task<IList<UserLoginInfo>>.FromResult((IList<UserLoginInfo>)loginInfos);
        }

        public Task RemoveLoginAsync(T user, UserLoginInfo login)
        {
            throw new NotImplementedException();
        }
    }

That’s quite a bit of implementing “do nothing” code just because Microsoft’s Framework complains about lack of implementation. After implementing all of those interface methods, though, things started to gel. Recalling Part 1 of this series, hearken back to ApplicationUserManager. This is the only class/place where IUserStore is utilized or injected. ApplicationUserManager’s Create method is the only thing left to update to utilize our UserStore:

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
    var manager = new ApplicationUserManager(new CustomUserStore<ApplicationUser>());
    .....

With all of that in place, including our previous updates to CheckPasswordAsync, we can login to the default MVC5 project utilizing our legacy authentication system and take advantage of the ASP.NET Identity framework. Going forward, if we wanted, we could hook into the Roles/Claims system to fully utilize the framework.

If anyone wants a fully working VS2013 demo solution, let me know.

One thought on “ASP.NET Identity (Part 2)”

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.