Introduction

ASP.NET Core Identity uses IPasswordHasher<TUser> to hash and verify passwords. The default implementation uses PBKDF2 with HMAC-SHA256. When password verification fails for valid credentials, it is usually due to a hash format mismatch (e.g., migrating from ASP.NET Identity 2 to ASP.NET Core Identity), custom seeding with incorrect hash format, or a changed password hasher implementation.

Symptoms

  • SignInManager.PasswordSignInAsync returns SignInResult.Failed for correct password
  • PasswordHasher.VerifyHashedPassword returns PasswordVerificationResult.Failed
  • User can log in on old system but not after migration
  • Seeded admin user cannot log in
  • Works for newly registered users but not for migrated users

Example debugging: ```csharp var hasher = new PasswordHasher<ApplicationUser>();

// Check the hash format var result = hasher.VerifyHashedPassword(user, user.PasswordHash, enteredPassword); // Returns Failed when hash format does not match

// Identity v3 hash format: // AQAAAAEAACcQAAAAE... (base64, starts with specific prefix) ```

Common Causes

  • Migrated from ASP.NET Identity v2 (different hash format) to v3
  • Seeded users with plain text passwords instead of hashed passwords
  • Custom IPasswordHasher implementation incompatible with default
  • Password hash column truncated in database migration
  • Different iteration count between hasher versions

Step-by-Step Fix

  1. 1.Verify hash format in the database:
  2. 2.```sql
  3. 3.-- Identity v3 (ASP.NET Core) hash starts with: AQAAAAEAACcQAAAAE
  4. 4.-- Identity v2 (ASP.NET 4.x) hash starts with: ABAAAAAAAACQAAAAQB
  5. 5.SELECT Id, Email, SUBSTRING(PasswordHash, 1, 30) as HashPrefix
  6. 6.FROM AspNetUsers;
  7. 7.`
  8. 8.Seed users with properly hashed passwords:
  9. 9.```csharp
  10. 10.public static async Task SeedAdminUserAsync(IServiceProvider services)
  11. 11.{
  12. 12.var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();

var admin = new ApplicationUser { UserName = "admin@example.com", Email = "admin@example.com", EmailConfirmed = true };

// Let Identity hash the password correctly var result = await userManager.CreateAsync(admin, "SecureP@ssw0rd!"); if (!result.Succeeded) { throw new Exception( $"Failed to create admin: {string.Join(", ", result.Errors.Select(e => e.Description))}"); }

await userManager.AddToRoleAsync(admin, "Admin"); } ```

  1. 1.Implement version-aware password hasher for migration:
  2. 2.```csharp
  3. 3.public class MigratingPasswordHasher<TUser> : PasswordHasher<TUser> where TUser : class
  4. 4.{
  5. 5.public override PasswordVerificationResult VerifyHashedPassword(
  6. 6.TUser user, string hashedPassword, string providedPassword)
  7. 7.{
  8. 8.// Try the default (v3) hasher first
  9. 9.var result = base.VerifyHashedPassword(user, hashedPassword, providedPassword);
  10. 10.if (result != PasswordVerificationResult.Failed)
  11. 11.return result;

// Fall back to v2 hash verification (ASP.NET Identity 2) if (VerifyV2Hash(providedPassword, hashedPassword)) { // Re-hash with v3 format var newHash = HashPassword(user, providedPassword); // Update would need to be saved by the caller return PasswordVerificationResult.SuccessRehashNeeded; }

return PasswordVerificationResult.Failed; }

private bool VerifyV2Hash(string password, string hashedPassword) { // Implement ASP.NET Identity v2 verification // Uses HMAC-SHA1 with 1000 iterations // ... return false; } }

// Register in DI builder.Services.AddSingleton<IPasswordHasher<ApplicationUser>, MigratingPasswordHasher<ApplicationUser>>(); ```

  1. 1.Reset password for affected users:
  2. 2.```csharp
  3. 3.// Generate a reset token and set new password
  4. 4.var user = await userManager.FindByEmailAsync("user@example.com");
  5. 5.var token = await userManager.GeneratePasswordResetTokenAsync(user);
  6. 6.var result = await userManager.ResetPasswordAsync(user, token, "NewP@ssw0rd!");

if (!result.Succeeded) { // Log the errors foreach (var error in result.Errors) { _logger.LogError("Password reset error: {Code} - {Description}", error.Code, error.Description); } } ```

Prevention

  • Always use UserManager.CreateAsync or UserManager.AddPasswordAsync to set passwords
  • Never insert raw password hashes into the database manually
  • Test login with seeded users after every database migration
  • Add integration tests that verify password hashing round-trip
  • Document the password hash version in use
  • Use PasswordVerificationResult.SuccessRehashNeeded to upgrade old hashes transparently