Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to migrate Identity users from a MVC5 app to a ASP.NET Core 2.2 app

I have a web app built on MVC5 using Identity. I want to convert this project to a ASP.NET Core 2.2 web app. I created a new ASP.NET Core 2.2 web application with authentication set to Individual User Accounts and added my database to the project by following these instructions. I then added a new Identity Scaffolded item to my project and added a migration and updated the database.

I registered a test user and when I checked SQL management server I see it created a new database for this project even though my connection string is for my old database.

I would like to keep my old database but convert it to use the new Identity Razor pages that are built in with ASP.NET Core. What is the best way to go about doing this?

like image 592
GlobalJim Avatar asked Dec 21 '18 01:12

GlobalJim


People also ask

Which instance holds the user identity in an ASP.NET page?

This instance is of type IPrincipal . IPrincipal is a special interface used to represent different identity types inside ASP.NET. It holds an IIdentity that represents the user identity plus its roles as an array of strings.


1 Answers

After upgrading the Identity tables, you may want to update existing users password hashes. Some new columns in the AspNetUsers table will have NULL values. First run this:

UPDATE AspNetUsers SET NormalizedEmail = UPPER(Email), NormalizedUserName = UPPER(UserName)
WHERE NormalizedEmail IS NULL

We need a way to differentiate what users are using the new hash version or not.

One way is to add a new property to IdentityUser:

public class ApplicationUser : IdentityUser
{
    public PasswordHashVersion HashVersion { get; set; }

    public ApplicationUser()
    {
        this.HashVersion = PasswordHashVersion.Core;
    }
}

public enum PasswordHashVersion
{
    OldMvc,
    Core
}

Existing users will have default PasswordHashVersion equals zero (OldMvc), new registered users will default to one (Core). If you have a smarter way to detect if a hash is from new or old algorithms, you don't need this.

Then we create a custom PasswordHash, which uses the old default hash algorithm implementation:

public class OldMvcPasswordHasher : PasswordHasher<ApplicationUser>
{
    public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPassword, string providedPassword)
    {
        // if it's the new algorithm version, delegate the call to parent class
        if (user.HashVersion == PasswordHashVersion.Core)
            return base.VerifyHashedPassword(user, hashedPassword, providedPassword);

        byte[] buffer4;
        if (hashedPassword == null)
        {
            return PasswordVerificationResult.Failed;
        }
        if (providedPassword == null)
        {
            throw new ArgumentNullException("providedPassword");
        }
        byte[] src = Convert.FromBase64String(hashedPassword);
        if ((src.Length != 0x31) || (src[0] != 0))
        {
            return PasswordVerificationResult.Failed;
        }
        byte[] dst = new byte[0x10];
        Buffer.BlockCopy(src, 1, dst, 0, 0x10);
        byte[] buffer3 = new byte[0x20];
        Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
        using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(providedPassword, dst, 0x3e8))
        {
            buffer4 = bytes.GetBytes(0x20);
        }
        if (AreHashesEqual(buffer3, buffer4))
        {
            user.HashVersion = PasswordHashVersion.Core;
            return PasswordVerificationResult.SuccessRehashNeeded;
        }
        return PasswordVerificationResult.Failed;
    }

    private bool AreHashesEqual(byte[] firstHash, byte[] secondHash)
    {
        int _minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length;
        var xor = firstHash.Length ^ secondHash.Length;
        for (int i = 0; i < _minHashLength; i++)
            xor |= firstHash[i] ^ secondHash[i];
        return 0 == xor;
    }
}

This class inherits the new Identity Core PasswordHasher. If the user's password hash version is already using the new algorithm (e.g HashVersion = Core), then we just call the base method from PasswordHasher which uses the new algorithm. Otherwise, use the old identity algorithm to verify the password.

If the password matches, we update the user password hash version to Core, and return PasswordVerificationResult.SuccessRehashNeeded to force updating the existing hash with the new algorithm.

Lastly, you need to make sure your custom PasswordHasher is being used. Add this to Startup.cs inside ConfigureServices:

// Replace the existing scoped IPasswordHasher<> implementation
services.Replace(new ServiceDescriptor(
    serviceType: typeof(IPasswordHasher<ApplicationUser>),
    implementationType: typeof(OldMvcPasswordHasher),
    ServiceLifetime.Scoped));

This must be added after any calls to AddIdentity, AddDefaultIdentity or AddIdentityCore.

This will slowly update password hashes as your users authenticate.

like image 60
Alisson Reinaldo Silva Avatar answered Oct 01 '22 03:10

Alisson Reinaldo Silva