ASP.NET Core  

Secure Password Storage in ASP.NET Core — Best Practices

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:

  • PBKDF2 hashing algorithm

  • Random per-user salt

  • 10,000+ iterations

  • Secure format: IdentityV3 password hash

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!