Masterly.Hash.AspNetCore
2.0.0
dotnet add package Masterly.Hash.AspNetCore --version 2.0.0
NuGet\Install-Package Masterly.Hash.AspNetCore -Version 2.0.0
<PackageReference Include="Masterly.Hash.AspNetCore" Version="2.0.0" />
<PackageVersion Include="Masterly.Hash.AspNetCore" Version="2.0.0" />
<PackageReference Include="Masterly.Hash.AspNetCore" />
paket add Masterly.Hash.AspNetCore --version 2.0.0
#r "nuget: Masterly.Hash.AspNetCore, 2.0.0"
#:package Masterly.Hash.AspNetCore@2.0.0
#addin nuget:?package=Masterly.Hash.AspNetCore&version=2.0.0
#tool nuget:?package=Masterly.Hash.AspNetCore&version=2.0.0
Masterly.Hash.AspNetCore

ASP.NET Core integration for Masterly.Hash:
- ASP.NET Core Identity adapter:
MasterlyIdentityPasswordHasher<TUser>replaces Identity'sPasswordHasher<TUser>. It still verifies the hashes Identity already wrote (v2 and v3 compatibility modes), so no user is locked out, andUserManagerupgrades each of them to Argon2id on that user's next successful login. - 503 on overload:
PasswordHasherBusyExceptionHandlermapsPasswordHasherBusyExceptionto503 Service UnavailablewithRetry-After. - Minimal API DoS guard:
.WithPasswordLengthLimit()rejects over-longSensitiveStringpasswords with400before the handler (and the hasher) runs.
Targets .NET 8 or later.
Install
dotnet add package Masterly.Hash.AspNetCore
Identity
builder.Services
.AddIdentityCore<AppUser>()
.AddEntityFrameworkStores<AppDbContext>()
.AddMasterlyPasswordHasher(); // optional: o => o.DefaultProfile = HashProfile.Fips2026a
AddMasterlyPasswordHasher:
- calls
AddMasterlyHash(fromMasterly.Hash.DependencyInjection), so the hasher is the same singleton asIPasswordHashereverywhere else, and options are validated at startup; - sets
ComputeRehashOnVerify = false(UserManager re-hashes onSuccessRehashNeededitself, so a second hash would be wasted work); yourconfigurestep runs after this and may change it; - lowers
ConcurrencyTimeoutfrom the core default of 30 s to 2 s (unless you set another value): Identity's hasher is synchronous, so a queued login blocks a thread-pool thread, and failing fast with 503 beats thread-pool starvation; - registers
AspNetIdentityV3FormatandAspNetIdentityV2FormatfromMasterly.Hash.Legacyunless you registered formats with those ids yourself (e.g. a v3 format with a different iteration cap); - replaces the
IPasswordHasher<TUser>registration and addsMasterlyPasswordLengthValidator<TUser>, soCreateAsync,ChangePasswordAsync,ResetPasswordAsyncandAddPasswordAsyncreturn a failedIdentityResult(codePasswordTooLong) for a password overMaxPasswordBytesinstead of throwing.
Results map one to one: Success, SuccessRehashNeeded (legacy format, outdated profile or pepper key) and Failed
(wrong password, empty password, or an unknown, malformed or over-cost stored hash; the adapter never throws for a bad
stored hash).
Without dependency injection:
var options = new PasswordHasherOptions { ComputeRehashOnVerify = false }.AddAspNetIdentityFormats();
var identityHasher = new MasterlyIdentityPasswordHasher<AppUser>(new PasswordHasher(options), options.MaxPasswordBytes);
503 with Retry-After
builder.Services.AddProblemDetails(); // required by UseExceptionHandler() on .NET 8
builder.Services.AddMasterlyHashExceptionHandler(); // o => o.RetryAfter = TimeSpan.FromSeconds(10)
...
app.UseExceptionHandler();
The response is 503, Retry-After: <seconds> (default 5), Cache-Control: no-store and a problem details body
(written through IProblemDetailsService when one is registered). Other exceptions go to the next handler. Register it
before any catch-all IExceptionHandler.
Password length limit (minimal APIs)
public sealed record LoginRequest(string UserName, SensitiveString Password);
app.MapPost("/login", async (LoginRequest request, IPasswordHasher hasher) => { ... })
.WithPasswordLengthLimit(); // also works on MapGroup(...)
The filter checks every SensitiveString argument and every public SensitiveString property of an argument (one
level deep) against PasswordHasherOptions.MaxPasswordBytes, measured the way the hasher measures it (UTF-8 after NFKC).
Security notes
- Identity v2 hashes are PBKDF2-HMAC-SHA1 with 1000 iterations. They are upgraded on login, but accounts that never log
in keep them: consider onion-wrapping them offline with
IPasswordHasher.TryWrap(seeMasterly.Hash.Legacy). - The v3 format refuses stored hashes whose PBKDF2 work exceeds 10,000,000 iterations. That caps, but does not remove,
the work a tampered row can force: seconds of CPU per attempt, holding a hasher slot. If you never raised Identity's
IterationCountfar above its default (100,000), register a tighter cap insideconfigure, e.g.o.AddLegacyFormats(new AspNetIdentityV3Format(1_000_000)). - Passwords are length-checked before NFKC normalization (which can expand some characters 18x): input longer than 8x
MaxPasswordBytesis refused from its length alone, both by the Identity adapter and by.WithPasswordLengthLimit(). MaxPasswordBytes(default 1024) also applies to existing Identity users: a longer legacy password no longer verifies, so raise the limit during migration if your users have such passwords. A legacy password that verifies but is over the limit after NFKC keeps its legacy hash (the adapter reportsSuccess, notSuccessRehashNeeded).MaxConcurrencybounds memory under a login flood; map the resultingPasswordHasherBusyExceptionto 503 so clients back off. Neither the 503 nor the 400 response contains the password or exception details.- Use
SensitiveStringfor password fields so they are redacted from logs and serialized output.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Masterly.Hash (>= 2.0.0)
- Masterly.Hash.DependencyInjection (>= 2.0.0)
- Masterly.Hash.Legacy (>= 2.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0 | 82 | 11/3/2026 |