Introduction
Passwords are the first line of defense in any application. Storing them incorrectly can lead to devastating breaches. ASP.NET Core Identity offers a secure, industry-standard approach—but only if used correctly.
In this article, we cover:
How ASP.NET Core stores passwords
Why you should never store plain text
Hashing vs encryption
Salting
Peppering
Additional security recommendations for production apps
How ASP.NET Core Stores Passwords
ASP.NET Core Identity stores passwords using:
This ensures no two passwords produce the same hash.
Never Store Plain Text Passwords
Example of what not to do:
user.Password = model.Password; //Insecure way of storing password
If your database gets leaked, all passwords are exposed.
Use Identity Password Hasher
ASP.NET Core Identity automatically hashes passwords:
await _userManager.CreateAsync(user, model.Password);
If you're building a custom user system:
var hashedPassword = _passwordHasher.HashPassword(user, password);
Verification
_passwordHasher.VerifyHashedPassword(user, hashedPassword, providedPassword);
Password Salting
Salt is a random string added before hashing to prevent:
Rainbow table attacks
Hash collisions
ASP.NET Core Identity generates salt automatically. No need to manage it yourself.
Password Peppering
Pepper is a secret stored outside the DB (in environment variables / Key Vault). It’s an optional extra security.
Example
string pepper = config["PasswordPepper"];
string passwordWithPepper = password + pepper;
var hash = _passwordHasher.HashPassword(user, passwordWithPepper);
Use only in custom implementations—not required for Identity.
Password Strength Requirements
Configure password policy:
builder.Services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequiredLength = 8;
});
Force Users to Change Password Regularly
Store last password change date and enforce:
if(user.LastPasswordChangeDate < DateTime.UtcNow.AddMonths(-3))
{
return Redirect("/Account/ForceChangePassword");
}
Prevent Password Reuse
Store password history hashes:
var oldHashes = await _db.PasswordHistory
.Where(x => x.UserId == user.Id)
.Select(x => x.HashedPassword)
.ToListAsync();
Reject if match
if(_passwordHasher.VerifyHashedPassword(user, oldHash, newPassword)
!= PasswordVerificationResult.Failed)
{
return "Password already used.";
}
Enable Email Confirmation
Enable email confirmation so only users with a valid email address can activate their account. This also prevents attackers from registering fake accounts.
Use HTTPS Everywhere
Always force to use HTTPS so that passwords and login data stay encrypted and safe while being sent between the user and the server.
app.UseHttpsRedirection();
Conclusion
Storing passwords securely requires:
Strong hashing (PBKDF2)
Salt (built-in)
Optional pepper
Strong password policy
HTTPS
Avoiding reuse
Periodic changes
ASP.NET Core Identity already does most of the heavy lifting—use it!