Skip to main content

ASP.NET Identity - Migrating users' passwords from ASP.NET Membership

· 2 min read
Serhii Korzh
Oleksandr Melnychenko

This is a third part of the series of articles about some not-so-well-known features and tricks in ASP.NET Identity. Here are you can find the [first]({{ 'ancid05' | internal_path }}) and the [second]({{ 'ancid02' | internal_path }}) parts.

Problem

This task usually appears when you need to transfer your old MVC web application to ASP.NET Core. If you use MVC version 3 or 4 and your application provides a user authentication service, then most likely this part is done with the old ASP.NET Membership library.

So, imagine you have a bunch of users, each of them has some password and the hash of that password stored in some database. Now you need to transfer all your current users to the new system built with ASP.NET Core. Of course, it's not a big problem to transfer their names, addresses, and other information. The problem is in those password hashes. ASP.NET Core Identity uses another hashing algorithm so all current users will not be able to access the system with their old passwords - the hashes will not match.

Solution

The solution is rather simple: we need to rewrite the default hashing service in ASP.NET Core Identity and make it "understand" both the old and new hashes.

Here is our class which implements IPassowrdHasher interface:

public class PasswordHasherWithOldMembershipSupport : IPasswordHasher<ApplicationUser>
{
//an instance of the default password hasher
IPasswordHasher<ApplicationUser> _identityPasswordHasher = new PasswordHasher<ApplicationUser>();

//Hashes the password using old algorithm from the days of ASP.NET Membership
internal static string HashPasswordInOldFormat(string password)
{
var sha1 = new SHA1CryptoServiceProvider();
var data = Encoding.ASCII.GetBytes(password);
var sha1data = sha1.ComputeHash(data);
return Convert.ToBase64String(sha1data);
}

//the passwords of the new users will be hashed with new algorithm
public string HashPassword(ApplicationUser user, string password) {
return _identityPasswordHasher.HashPassword(user, password);
}

public PasswordVerificationResult VerifyHashedPassword(ApplicationUser user,
string hashedPassword, string providedPassword)
{
string pwdHash2 = HashPasswordInOldFormat(providedPassword);


if (hashedPassword == pwdHash2) {
//first we check the hashed password with "old" hash
return PasswordVerificationResult.Success;
}
else {
//if old hash doesn't work - use the default approach
return _identityPasswordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword);
}
}
}

After that we just need to register our new IPasswordHasher implementation in the DI container:

//Startup.cs
. . . . . .
public void ConfigureServices(IServiceCollection services)
{
. . . . . .

services.AddSingleton<IPasswordHasher<ApplicationUser>, PasswordHasherWithOldMembershipSupport>();
}