Create Custom Password Hasher for ASP .NET Core Identity - .NET Core 3 Update
This post is an update to my previous post on how to create a custom password hasher in dotnet core 2. It contains updated code for working with dotnet core 3.x, the Visual Studio 2019 MVC project template, and updated screenshots. The end result is the same, a custom password hasher for the ASP .NET Core Identity system.
ASP .NET Core Identity is very extendable and extending it is a good way to get a better understanding of how the system works. We can customize it by providing new implementations to its interfaces. This code is not production ready and any custom security implementation should be done with care and be backed by good reasons. That being said, lets have some fun. This tutorial requires Visual Studio 2019, dotnet core 3.x, and LocalDb, which should be installed with Visual Studio depending on which components were picked at the time of install. The entire sample project is available on the master branch on Github.
Start by opening Visual Studio 2019 and create a new ASP .NET Core Web Application project. Click Next.
Provide a name for the project. I named it DotNetCoreAuthExamples.CustomPasswordHasher but you can name it anything you like. Click Create
Choose .NET Core and ASP.NET Core 3.x, depending on your version, from the dropdowns at the top. Select Web Application (Model-View-Controller) for the template and under the Authentication header click Change and pick Individual User Accounts. Click Create.
After the project is created, debug it from Visual Studio. Once the website loads, click the Register link from the top menu bar. Fill in an email and password and click the Register button.
The first time an error will appear. This is because we haven’t created the database yet. Click on Apply Migrations. This will create the database using Entity Framework. All the required database stuff is defined by the template and the defaults will work for this demonstration.
Once that is done, the button text changes to Migrations Applied. Refresh the page and click yes at any browser prompts about resubmitting the form. A registration confirmation page loads, click "Click here to confirm your account".
If you get an error starting with SqlException: A network-related or instance-specific error occurred while establishing a connection to SQL Server... check the server in the app settings connection string. This can be found in the appsettings.json file. The Visual Studio Sql Server Object Explorer will tell you the server path to LocalDb. Make sure the connection string matches this path. If not, change it and then stop and restart debugging.
After the page reloads, a record is also created in the database for this user. To see that, open the SQL Server Object Explorer in Visual Studio. It should auto-connect to LocalDb. The database name will contain the project name. Once you find it, query the AspNetUsers table like this SELECT * FROM dbo.AspNetUsers. There is 1 row with the registered user.
As this point, we have a working default MVC template with Individual User Accounts. We will now supplement this with a class to do our own password hashing. This is to demonstrate how different components of the Identity membership system can be customized. Create a new class named CustomPasswordHasher and have it implement IPasswordHasher<IdentityUser>. There are 2 methods we need to implement, string HashPassword(TUser user, string password) and PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword). Our custom class will reverse the original password string. This is to demonstrate how the methods will be used. In a real application, this would contain a secure, well planned, and well tested implementation. The full class is below. This should not be used in a real application.
public class CustomPasswordHasher : IPasswordHasher<IdentityUser>
{
public string HashPassword(IdentityUser user, string password)
{
return ReversePassword(password);
}
public PasswordVerificationResult VerifyHashedPassword(IdentityUser user, string hashedPassword, string providedPassword)
{
if (hashedPassword == ReversePassword(providedPassword))
{
return PasswordVerificationResult.Success;
}
return PasswordVerificationResult.Failed;
}
private string ReversePassword(string value)
{
// This is not a secure way to store a password!
char[] charArray = value.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}
The final thing to do is register our new implementation with the dependency injection system. This happens in the
ConfigureServices method of the Startup class. Copy/Paste the following line as the first line of the method: services.AddTransient<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();. To learn more about Dependency injection in ASP .NET Core, check out my post "ASP .NET Core MVC Dependency Injection Overview". The full ConfigureServices method is below.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IPasswordHasher<IdentityUser>, CustomPasswordHasher>();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
}
The only thing left to do is test it. Place a breakpoint in both the HashPassword and VerifyHashedPassword methods. Start debugging and register a new user, like we did above. On registration, the HashPassword breakpoint will be hit and on login the VerifyHashedPassword method will be hit. In both cases, the user will be logged in. If you got this far, it means you have successfully created a custom password hasher for ASP .NET Core Identity!
In conclusion, this post shows 1 example of overriding a feature of ASP .NET Core Identity in .NET core 3.x. The complete code can be found on the master branch in this Github repository. This code is for educational purposes only and is not meant for production environments. Thought, planning, and good reasons need to be involved for authentication code meant for a production environment. That being said, it is possible to customize many parts of the ASP .NET Core Identity system.
