How to validate password in .NET

Introduction
A password policy that forces P@ssw0rd1 past validation while rejecting a genuinely strong 20-character passphrase is worse than no policy at all - it trains users toward predictable substitutions instead of actual entropy. This video builds password validation in .NET the right way: composable rules instead of one unreadable regex, and a look at what ASP.NET Core Identity's own PasswordOptions actually enforces.
🎬 Watch the full video here:
One regex is not a policy
The instinctive approach is a single pattern trying to enforce everything at once:
Regex.IsMatch(password, @"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[\W_]).{8,}$");
It works, but it's unreadable, untestable in isolation, and gives the user one useless error message ("password doesn't meet requirements") instead of telling them which rule failed.
Composable validators instead
Splitting each requirement into its own small check makes the failure message specific and each rule independently testable:
public interface IPasswordRule
{
bool IsValid(string password);
string ErrorMessage { get; }
}
public class MinimumLengthRule(int minLength) : IPasswordRule
{
public bool IsValid(string password) => password.Length >= minLength;
public string ErrorMessage => $"Password must be at least {minLength} characters.";
}
A validator runs the full set and collects every failure, not just the first one:
var failures = rules.Where(rule => !rule.IsValid(password))
.Select(rule => rule.ErrorMessage)
.ToList();
Adding a new rule (say, "must not appear in a known breached-password list") means adding one more class, not re-deriving a bigger regex.
Length matters more than complexity
Current guidance (NIST's own password guidelines among them) leans toward length over forced complexity. A 16-character passphrase like correct-horse-battery has far more entropy than an 8-character P@ssw0rd! that satisfies a complexity rule, and complexity rules reliably push users toward predictable substitutions (@ for a, 1 for l) that attackers already account for in their guessing tools. A minimum length rule (12+ characters) does more real work than requiring a symbol.
What Identity already does for you
If the app uses ASP.NET Core Identity, this is largely already built - PasswordOptions on IdentityOptions covers length, digit, uppercase, lowercase, and non-alphanumeric requirements out of the box:
builder.Services.Configure<IdentityOptions>(options =>
{
options.Password.RequiredLength = 12;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireDigit = true;
});
For anything Identity doesn't cover out of the box - a check against a breached-password API, a check that the password doesn't contain the username - implement IPasswordValidator<TUser> and register it alongside the built-in one rather than replacing Identity's validation wholesale.
Common pitfalls
- One unmaintainable regex. Hard to read, hard to test, gives a useless error message.
- Overweighting complexity, underweighting length. A short "complex" password is often weaker than a long simple one.
- Silently truncating long passwords. Some legacy systems cap input length; make any limit generous and documented.
- Client-side-only validation. Always re-validate server-side; the client-side check is a UX nicety, not security.
- Rejecting passphrases with spaces or common punctuation for no real security reason.
Key Takeaways
- Split password rules into small, independently testable validators instead of one dense regex.
- Report every failed rule, not just the first, so the user knows exactly what to fix.
- Favor a longer minimum length over forced complexity - it produces genuinely stronger passwords.
- ASP.NET Core Identity's
PasswordOptionsalready covers the common rules; extend it withIPasswordValidator<TUser>for anything custom. - Always validate server-side regardless of what the client does.
Get the Full Source Code
The complete runnable solution - the composable rule interfaces, the validator, and the Identity PasswordOptions wiring - is available to Patreon supporters. If you want the rule set ready to drop into your own registration flow instead of rebuilding it from the walkthrough above, you can find it on Patreon.