Password authentication

Hash passwords with a trusted library

Store password verifiers with a current password-hashing algorithm and library instead of encryption or a fast general-purpose hash.

Never store a password in plaintext. Never store it encrypted either, because whoever gets the key gets every password. The goal is different: if someone steals the whole database, guessing each password should still be slow and expensive.

That’s what a password hashing algorithm does. It’s deliberately slow, and it’s one-way. You can check a password against the stored value, but you can’t get it back out.

Use Argon2id, through a library

The current OWASP recommendation is Argon2id, with parameters tuned for your environment. Don’t implement it yourself, and don’t build your own construction out of SHA-256 and a loop. Use a maintained library, and let it generate a unique salt per password and encode the algorithm and parameters inside the result.

// Library API names differ. Use the maintained library you selected.
const passwordHash = await passwordHasher.hash(password)
await users.create({ id, email, passwordHash })

What comes back is one string that looks like $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>. Algorithm, work parameters, salt and hash travel together. At login the library reads the parameters out of that string and verifies with them.

The salt is not a secret

The salt is a random value mixed into every hash. It doesn’t need to be hidden. It makes two users with the same password end up with different stored values, so an attacker has to crack each account separately instead of using one precomputed table for everyone.

Tune the work factor

The parameters decide how slow hashing is. Slow is the point, but only up to a level a real user won’t notice. Tune them on production-like hardware, not on your laptop.

Measure a single login, then measure fifty at once. A setting that takes 100 ms alone can exhaust memory under load, because Argon2id is memory-hard and every concurrent login holds its own chunk of RAM.

Parameters age, so rehash on login

Hardware gets faster and guidance changes, so hashes from three years ago may be weaker than you’d choose today. You can’t rehash them in bulk, you don’t have the passwords.

But you do have the password for a moment at every successful login. Verify it, then look at the encoded parameters. If they’re below the current policy, hash the password again with the new settings and replace the stored value. Active accounts upgrade themselves.

A note on peppers

A pepper is a server-side secret added to every hash and kept outside the database. It helps when the database leaks but the secret doesn’t. It also gives you a key to manage and rotate, and losing it breaks every login. Don’t add one unless you can run that safely.

Try this: pick a library, hash one password, and benchmark it on a production-like machine. Write down the single-login latency, the behavior under concurrent logins, the library version, the parameters, and the rule that triggers a rehash.

Lesson completed