Avoid password reuse with Pwned Passwords and ASP.NET Core 🔒
Let's face it. Password re-use is something that should be avoided. Breach after breach are published and giant lists of users unhashed passwords available for hackers to exploit. In this post, I'll show you how we are avoiding people to re-use pwned passwords on elmah.io in our ASP.NET Core web app.
In this post, I want to introduce you to Pwned Passwords and the PwnedPasswords.Client
NuGet package. Pwned Passwords is an excellent service created by Troy Hunt. It's basically a REST API where you provide a user's hashed password and get a reply if that password is already in one or more password breaches. The API is free as long as you clearly attribute it where you consume data. If you like the API you can consider making a donation directly to Troy Hunt through the https://haveibeenpwned.com/ website.
Let's build an example in ASP.NET Core MVC where we utilize Pwned Passwords. You can either call the API through a HttpClient
or use the nice little helper PwnedPasswords.Client
created by Andrew Lock. In my test application I have an endpoint for users to call when signing up:
[HttpPost]
[Route("signup")]
public async Task<ActionResult> SignUp([FromForm]SignUpViewModel model)
{
if (!ModelState.IsValid) return BadRequest();
// ...
return Ok();
}
The browser will send an HTTP POST to this endpoint and provide the posted form as properties within the SignUpViewModel
class through data-binding:
public class SignUpViewModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
The // ...
in the controller action represents the code where you would create the user in a local database or however you want to store user data.
The ModelState.IsValid
property will verify that Username
and Password
have values and if not, return a bad request to the browser. After validating the password has a value, we want to extend the code to validate the password isn't pwned. Start by installing the PwnedPasswords.Client
NuGet package:
dotnet add package PwnedPasswords.Client
The client is configured through dependency injection which follows how the rest of ASP.NET Core nicely. Include the following code in the ConfigureServices
method in the Startup.cs
file:
services.AddPwnedPasswordHttpClient(minimumFrequencyToConsiderPwned: 1)
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2)));
The AddOwnedPasswordHttpClient
method will register an IPwnedPasswordsClient
instance in your application. The minimumFrequencyToConsiderPwned
parameter tells PwnedPasswords.Client
when to consider a password as pwned. In this example, I set it to 1 to never allow a password found in any breaches. The rest of the code configure retry when calling the Pwned Passwords API. If you haven't already, you need to install the Polly
and Microsoft.Extensions.Http.Polly
NuGet packages for this code to compile.
We are ready to start checking inputted passwords. Add a dependency to the constructor of the signup controller:
private readonly IPwnedPasswordsClient pwnedPasswords;
public SignupController(IPwnedPasswordsClient pwnedPasswords)
{
this.pwnedPasswords = pwnedPasswords;
}
Finally, validate the posted password in the SignUp
method:
public async Task<ActionResult> SignUp([FromForm]SignUpViewModel model)
{
if (!ModelState.IsValid) return BadRequest();
if (await pwnedPasswords.HasPasswordBeenPwned(model.Password)) return BadRequest();
// ...
return Ok();
}
The HasPasswordBeenPwned
method will return true if the inputted password is found in one or more breaches. In this case, I return a bad request to the browser. You may want to include an error code, validation message, or similar back to the client in order to show a nice error message to the user.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website