Migrating an Existing Website from SimpleMembership to ASP.NET Identity

ASP.NET Identity is replacing SimpleMembership as the security/membership framework to use when creating ASP.NET applications.  If you are moving to MVC 5 and Visual Studio 2013 the ASP.NET templates generate code that uses ASP.NET Identity.  ASP.NET Identity is much more extensible than SimpleMembership is and it uses the new OWIN architecture available in MVC 5.  I think that ASP.NET Identity is an improvement over SimpleMembership and the old membership provider model that historically was used with ASP.NET applications.  The main problem I have with it is the lack of documentation that is currently available.  One article I was looking for in particular was how to migrate an existing website that uses SimpleMembership to ASP.NET Identity.  You can move to MVC 5 and keep using SimpleMembership as I wrote about here.  But what if you wanted to take advantage of benefits and features of ASP.NET Identity in a legacy website.  Microsoft wrote about Migrating an Existing Website from SQL Membership to ASP.NET Identity, and Migrating Universal Provider Data for Membership and User Profiles to ASP.NET Identity, but nothing on migrating SimpleMembership.

So I researched this more and it turns out to be very simple (no pun intended) to migrate from SimpleMembership to ASP.NET Identity. The main issue with migrating is that you will need to move your existing membership data to the new database schema used by ASP.NET Identity.   And the password hash used by SimpleMembership must match the one used by ASP.NET Identity.  More on the password hash later.

To move your data over you first need to create a custom ApplicationUser that has the same properties as your UserProfile in SimpleMembership.  For example, if your UserProfile looked like this;

    public class UserProfile
    {
        [Key]
        [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Email { get; set; }
    }

Then your new ApplicaitonUser class will look like this;

    public class ApplicationUser : IdentityUser
    {
        public string Email { get; set; }
        public string ConfirmationToken { get; set; }
        public bool IsConfirmed { get; set; }
        public string PasswordResetToken { get; set; }
    }

In this example the custom property added to UserProfile is Email.  You will notice that I did not add the properties UserId and UserName to the new ApplicationUser class.  That is because they are already defined in the class it inherits, IdentityUser.  The definition of IdentityUser looks like this.

   public class IdentityUser : IUser
    {
        public IdentityUser();
        public IdentityUser(string userName);

        public virtual ICollection<IdentityUserClaim> Claims { get; }
        public virtual string Id { get; set; }
        public virtual ICollection<IdentityUserLogin> Logins { get; }
        public virtual string PasswordHash { get; set; }
        public virtual ICollection<IdentityUserRole> Roles { get; }
        public virtual string SecurityStamp { get; set; }
        public virtual string UserName { get; set; }
    }

UserName is a direct map from UserProfile to ApplicationUser. You will notice that the Id for IdentityUser is a string.  You will have to account for this in the scripts or application used to import the existing data when you map UserProfile.UserId to ApplicationUser.Id.

ASP.NET Identity flattens the table layout by keeping all of the membership data in a single table called AspNetUsers.  In SimpleMembership there were two tables, UserProfile and webpages_Membership.  webpages_Membership contained information like the Password, IsConfirmed and ConfirmationToken.  So when mapping passwords you will need to map webpagesMembership.Password to AspNetUsers.PasswordHash.  SimpleMembership had a PasswordSalt field, but it was never used. More on that later.  The IsConfirmed and ConfirmationToken are not available in IdentityUser so that is why I added them to the ApplicationUser class. You only need to add these if you are using email confirmation in SimpleMembership or are planning on using it going forward.   The same goes for PasswordResetToken used for password resets.

Once you have ApplicationUser defined to mimic your custom UserProfile in SimpleMembership, create the new database, and run your import script or application. If you were using roles you will need to import the role data as well.  It is pretty straight map from wepages_Roles to AspNetRoles except while the id's for webpages_Roles is an int they id in AspNetRoles is nvarchar(128).  The same applies when mapping wepages_UsersInRoles to AspNetUserRoles.

So now you have all of your old SimpleMembership data in your shiny new ASP.NET Identity tables will it work without further modifications. This will only work if the password hash used by SimpleSecurity is the same as ASP.NET Identity.  Well it turns out they are the same.

At first glance I thought they were different because entering the same password in SimpleSecurity gave different results when entered into ASP.NET Identity.  The fact that the PasswordSalt field was empty should have been a clue to what I was seeing.  I did some research and found that SimpleMembership uses System.Web.Helpers.Crypto to hash and verify the passwords.  To hash the password the method HashPassword is used. The password hash is generated with the RFC 2898 algorithm using a 128-bit salt, a 256-bit subkey, and 1000 iterations. The format of the generated hash bytestream is {0x00, salt, subkey}, which is base-64 encoded before it is returned.  So the reason it was different each time was that this method creates a new salt each time and it is encoded in what I thought was just the straight password hash. I then ran a quick test to validate that ASP.NET Identity could verify a password hash from SimpleMembership and it worked perfectly.

What if they did use different password hash algorithms.  It is very easy to plug in your own password hasher in ASP.NET Identity.  First you just write a class that implements the IPasswordHasher interface.  Here is one that uses the password hasher used by SimpleMembership.

 
    public class SimplePasswordHasher : IPasswordHasher
    {
        public string HashPassword(string password)
        {
            return Crypto.HashPassword(password);
        }

        public PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
        {
            if(Crypto.VerifyHashedPassword(hashedPassword, providedPassword))
                return PasswordVerificationResult.Success;
            else return PasswordVerificationResult.Failed;
        }
    }

And here is how we plug it into the AccountController.

 
        public AccountController(UserManager<ApplicationUser> userManager)
        {
            userManager.PasswordHasher = new SimplePasswordHasher();
            UserManager = userManager;
        }

So all we have to do to migrate our SimpleMembership website to ASP.NET Identity is create an ApplicationUser class that mimic the old UserProfile class and move the data over. Simple, isn't it.

Checkout an update to this article here.

Comments

Popular posts from this blog

Using Claims in ASP.NET Identity

Seeding & Customizing ASP.NET MVC SimpleMembership

Customizing Claims for Authorization in ASP.NET Core 2.0