Created
June 4, 2022 07:05
-
-
Save hgirish/f234d65bfc23a7ff5f7f62efe2f8b817 to your computer and use it in GitHub Desktop.
Update Asp.Net Identity to Latest .net Core Identity, tested with Net6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackoverflow.com/questions/53878000/how-to-migrate-identity-users-from-a-mvc5-app-to-a-asp-net-core-2-2-app | |
/* 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: | |
*/ | |
using Microsoft.AspNetCore.Identity; | |
public class ApplicationUser: IdentityUser | |
{ | |
public PasswordHashVersion HashVersion { get; set; } | |
public ApplicationUser() | |
{ | |
HashVersion = PasswordHashVersion.Core; | |
} | |
} | |
public enum PasswordHashVersion | |
{ | |
OldMvc, | |
Core | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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. | |
*/ | |
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//This must be added after any calls to AddIdentity, AddDefaultIdentity or AddIdentityCore. | |
// Replace the existing scoped IPasswordHasher<> implementation | |
services.Replace(new ServiceDescriptor( | |
serviceType: typeof(IPasswordHasher<ApplicationUser>), | |
implementationType: typeof(OldMvcPasswordHasher), | |
ServiceLifetime.Scoped)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Script to update Asp.Net Identity tables to Asp.Net Core or Net 6 Identity tables | |
IF (not EXISTS (SELECT * | |
FROM INFORMATION_SCHEMA.TABLES | |
WHERE TABLE_SCHEMA = 'dbo' | |
AND TABLE_NAME = 'AspNetUserTokens')) | |
Begin | |
CREATE TABLE dbo.[AspNetUserTokens]( | |
[UserId] [nvarchar](128) NOT NULL, | |
[LoginProvider] [nvarchar](128) NOT NULL, | |
[Name] [nvarchar](128) NOT NULL, | |
[Value] [nvarchar](max) NULL, | |
CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY CLUSTERED | |
( | |
[UserId] ASC, | |
[LoginProvider] ASC, | |
[Name] ASC | |
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] | |
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] | |
end | |
GO | |
IF (not EXISTS (SELECT * | |
FROM INFORMATION_SCHEMA.TABLES | |
WHERE TABLE_SCHEMA = 'dbo' | |
AND TABLE_NAME = 'AspNetRoleClaims')) | |
Begin | |
CREATE TABLE [dbo].[AspNetRoleClaims]( | |
[Id] [int] IDENTITY(1,1) NOT NULL, | |
[RoleId] [nvarchar](128) NOT NULL, | |
[ClaimType] [nvarchar](max) NULL, | |
[ClaimValue] [nvarchar](max) NULL, | |
CONSTRAINT [PK_AspNetRoleClaims] PRIMARY KEY CLUSTERED | |
( | |
[Id] ASC | |
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] | |
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] | |
end | |
GO | |
IF (OBJECT_ID('FK_AspNetRoleClaims_AspNetRoles_RoleId', 'F') IS NULL) | |
Begin | |
ALTER TABLE [dbo].[AspNetRoleClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] FOREIGN KEY([RoleId]) | |
REFERENCES [dbo].[AspNetRoles] ([Id]) | |
ON DELETE CASCADE | |
end | |
GO | |
ALTER TABLE [dbo].[AspNetRoleClaims] CHECK CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] | |
GO | |
IF (OBJECT_ID('FK_AspNetUserTokens_AspNetUsers_UserId', 'F') IS NULL) | |
Begin | |
ALTER TABLE [dbo].[AspNetUserTokens] WITH CHECK ADD CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] FOREIGN KEY([UserId]) | |
REFERENCES [dbo].[AspNetUsers] ([Id]) | |
ON DELETE CASCADE | |
end | |
GO | |
ALTER TABLE [dbo].[AspNetUserTokens] CHECK CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] | |
GO | |
IF COL_LENGTH('dbo.AspNetUsers', 'NormalizedUserName') IS NULL | |
begin | |
alter table dbo.AspNetUsers | |
add NormalizedUserName nvarchar(256), | |
NormalizedEmail nvarchar(256), | |
ConcurrencyStamp nvarchar(max), | |
LockoutEnd datetimeOffset | |
end | |
go | |
IF COL_LENGTH('dbo.AspNetUserLogins', 'ProviderDisplayName') IS NULL | |
begin | |
alter table dbo.AspNetUserLogins add ProviderDisplayName nvarchar(max) | |
end | |
go | |
IF COL_LENGTH('dbo.AspNetRoles', 'NormalizedName') IS NULL | |
begin | |
alter table dbo.AspNetRoles | |
add NormalizedName nvarchar(256), | |
ConcurrencyStamp nvarchar(max) | |
end | |
go | |
UPDATE dbo.AspNetUsers SET NormalizedEmail = UPPER(Email), NormalizedUserName = UPPER(UserName) | |
WHERE NormalizedEmail IS NULL | |
go | |
update dbo.AspNetRoles set NormalizedName = upper(Name) where NormalizedName is null | |
go | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment