Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core Identity - Extending Password Hasher

I'm working towards moving an application from Web Forms to MVC and opted to go with MVC 6 using ASP.NET Core.

In my current application I have a custom password hasher used with Identity. The implementation is very simple in my custom UserManager class:

public ApplicationUserManager()
  : base(new UserStore<IdentityUser>(new AuthContext()))
{
    this.PasswordHasher = new SqlPasswordHasher();
}

I'm trying to do the same with .NET Core but the PasswordHasher property doesn't exist in UserManager. I see that the constructor will take an IPasswordHasher parameter so I tried this:

public ApplicationUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor,
        IPasswordHasher<ApplicationUser> passwordHasher, IEnumerable<IUserValidator<ApplicationUser>> userValidators,
        IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer,
        IdentityErrorDescriber errors, IServiceProvider serviceProvider, ILogger<UserManager<ApplicationUser>> logger)
  : base(store, optionsAccessor, new SqlPasswordHasher(), userValidators, passwordValidators, keyNormalizer, errors,
        serviceProvider, logger)
{
}

In SqlPasswordHasher I'm simply overriding the VerifyHashedPassword method which looks like this:

public override PasswordVerificationResult VerifyHashedPassword(ApplicationUser user, string hashedPassword, string providedPassword)
{
    // My custom logic is here
    ...
}

However, the above doesn't work. I have a breakpoint set in the VerifyHashedPassword method of SqlPasswordHasher and it doesn't get triggered.

I thought I was going about this the wrong way and I should be utilizing DI to accomplish this. I updated the constructor of my user manager so that it doesn't instantiate a new SqlPasswordHasher, but uses the default interface parameter instead:

public ApplicationUserManager(IUserStore<ApplicationUser> store, IOptions<IdentityOptions> optionsAccessor,
        IPasswordHasher<ApplicationUser> passwordHasher, IEnumerable<IUserValidator<ApplicationUser>> userValidators,
        IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators, ILookupNormalizer keyNormalizer,
        IdentityErrorDescriber errors, IServiceProvider serviceProvider, ILogger<UserManager<ApplicationUser>> logger)
  : base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors,
        serviceProvider, logger)
{
}

Then in Startup.cs I added a scoped service:

services.AddScoped<IPasswordHasher<ApplicationUser>, SqlPasswordHasher>();

But again, this doesn't work and the breakpoint in SqlPasswordHasher is never triggered.

I have a similar line for my custom Sign In Manager:

services.AddScoped<SignInManager<ApplicationUser>, ApplicationSignInManager>();

That works great. The ApplicationSignInManager takes a UserManager parameter and I can see that the UserManager takes an IPasswordHasher parameter.

I'm assuming SignInManager uses the UserManager which uses the PasswordHasher. So my question is, how do I get the UserManager to use my custom Password Hasher? Or if thats not the case, how do I get the SignInManager to use my Password hasher?

EDIT: I've been able to confirm that when my ApplicationUserManager is instantiated, my SqlPasswordHasher is being used in the constructor so the DI is working properly. I just can't figure out why my override of VerifyHashedPassword is not being triggered.

like image 788
Mat H. Avatar asked May 19 '16 20:05

Mat H.


1 Answers

It turns out the problem was not related to code at all. Adding my SqlPasswordHasher to the services via

services.AddScoped<IPasswordHasher<ApplicationUser>, SqlPasswordHasher>();

worked perfectly.

The problem was with how I migrated the data. Since I was using an existing database that was being used with an older version of Identity, I had to add the following fields to my existing AspNetUsers table:

NormalizedUserName
ConcurrencyStamp
LockoutEnd
NormalizedEmail

However I didn't populate the NormalizedUserName or NormalizedEmail fields. So that's why it was never triggering my override of VerifyHashedPassword; because it never found my user since it was looking up based on NormalizedUserName.

Once I populated those fields it started triggering my VerifyHashedPassword method.

like image 78
Mat H. Avatar answered Oct 16 '22 08:10

Mat H.