andrewlock / PwnedPasswords

An ASP.NET Core Identity validator that checks for PwnedPasswords
MIT License
103 stars 12 forks source link

PwnedPasswords logo

Pwned Passwords are hundreds of millions of real world passwords exposed in data breaches. This exposure makes them unsuitable for ongoing use as they're at much greater risk of being used to take over other accounts. They're searchable online below as well as being downloadable for use in other online system

Build status

client-nuget validator-nuget

PwnedPasswords.Client and PwnedPasswords.Validator

This repository contains two libraries, PwnedPasswords.Client and PwnedPasswords.Validator.

Why should you care?

As per Troy Hunt's website:

Password reuse and credential stuffing

Password reuse is normal. It's extremely risky, but it's so common because it's easy and people aren't aware of the potential impact. Attacks such as credential stuffing take advantage of reused credentials by automating login attempts against systems using known emails and password pairs.

NIST's guidance: check passwords against those obtained from previous data breaches

The Pwned Passwords service was created after NIST released guidance specifically recommending that user-provided passwords be checked against existing data breaches . The rationale for this advice and suggestions for how applications may leverage this data is described in detail in the blog post titled Introducing 306 Million Freely Downloadable Pwned Passwords.

This package provides an IPasswordValidator for ASP.NET Core Identity that checks whether the provided password appears on the have I been pwned list.

PwnedPasswords.Client

.NET Core 2.1 introduces HTTPClient factory, an "opinionated factory for creating HttpClient instances". It allows easy configuration of HttpClient instances, manages their lifetime, and enables easy addition of common functionality, such as retry logic for transient HTTP errors.

PwnedPasswords.Client provides the IPwnedPasswordsClient type, which can be used to easily access the PwnedPasswords API. It hooks into the Microsoft.Extensions.DependencyInjection / ASP.NET Core DI container, and can be configured with optional fault handling etc as required.

Getting started

Install the PwnedPasswords.Client NuGet package into your project using:

dotnet add package PwnedPasswords.Client

When you install the package, it should be added to your csproj. Alternatively, you can add it directly by adding:

<PackageReference Include="PwnedPasswords.Client" Version="1.2.0" />

Add to your dependency injection container in Startup.ConfigureServices using the AddPwnedPasswordHttpClient() extension method. You can further configure the IHttpClientBuilder to add fault handling for example.

public void ConfigureServices(IServiceCollection services)
{
    services.AddPwnedPasswordHttpClient()                      // add the client to the container
        .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))     //configure the HttpClient used by the IPwnedPasswordsClient
        .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(2)));

    // other configuration
}

You can also choose the minimum number of times a password must have appeared in a breach for it to be considered "pwned". So for example, if you only want to consider passwords that have appeared 20 times as pwned you can use the overload on AddPwnedPasswordHttpClient():

public void ConfigureServices(IServiceCollection services)
{
    services.AddPwnedPasswordHttpClient(minimumFrequencyToConsiderPwned: 20);
}

You can also configure this using the standard Options pattern in ASP.NET Core, for example by loading the required value from a JSON value.

public void ConfigureServices(IServiceCollection services)
{
    services.AddPwnedPasswordHttpClient();
    services.Configure<PwnedPasswordsClientOptions>(Configuration.GetSection("PwnedPasswords"));
}

PwnedPasswords.Validator

PwnedPasswords.Validator contains an implementation of an ASP.NET Core Identity IPasswordValidator that verifies the provided password has not been exposed in a known security breach.

Getting started

Install the PwnedPasswords.Validator NuGet package into your project using:

dotnet add package PwnedPasswords.Validator

When you install the package, it should be added to your csproj. Alternatively, you can add it directly by adding:

<PackageReference Include="PwnedPasswords.Validator" Version="1.2.0" />

You can add the PwnedPasswords ASP.NET Core Identity Validator to your IdentityBuilder in Startup.ConfigureServices using the AddPwnedPasswordValidator() extension method.

public void ConfigureServices(IServiceCollection services)
{
     services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddPwnedPasswordValidator<IdentityUser>(); // add the validator

    // other configuration
}

As for the PwnedPasswordsClient library, you can customize the minimum number of times a password must have appeared in a breach for it to be considered invalid by the validator. For example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddPwnedPasswordValidator<IdentityUser>(); // add the validator

    // set the minimum password to consider the password pwned
    services.Configure<PwnedPasswordsClientOptions>(Configuration.GetSection("PwnedPasswords"));
}

Customizing Error Messages

For simple scenarios, you can customize the error message for invalid passwords by configuring the validation options. For example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddPwnedPasswordValidator<IdentityUser>(options => 
            options.ErrorMessage = "My custom message"); // set the error message
}

If you need more control over the message - for example, if you want to use the request culture to localize the message text - you can implement your own subclass of PwnedPasswordErrorDescriber. For example:

public class CustomErrorDescriber : PwnedPasswordErrorDescriber
{
    public override IdentityError PwnedPassword()
    {
        return new IdentityError
        {
            Code = nameof(PwnedPassword),
            Description = Resources.PwnedPasswordError
        };
    }
}

You can register your custom class by using the AddPwnedPasswordErrorDescriber extension method.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDefaultIdentity<IdentityUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddPwnedPasswordValidator<IdentityUser>()
        .AddPwnedPasswordErrorDescriber<CustomErrorDescriber>();
}

Additional Resources