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?
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.
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
orAddIdentityCore
.
This will slowly update password hashes as your users authenticate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With