Guides/Networking & Security
Networking & Security6 min read

How to Store Passwords Securely in Your Application

Storing passwords incorrectly is one of the most common and catastrophic security mistakes. This guide covers what to use (bcrypt/argon2), what never to use (MD5, SHA1, plain SHA-256), and how to implement it correctly.

Never Store Plaintext Passwords

Storing passwords in plaintext means a single database breach exposes every user's password — which they likely reuse on other sites. Encrypting passwords (reversible) is also wrong — if your encryption key is compromised, all passwords are exposed. The correct approach is one-way hashing with a slow, salted algorithm designed for passwords. This means even if your database is stolen, individual passwords remain protected.

Use bcrypt or Argon2

bcrypt and Argon2 are purpose-built password hashing algorithms. They are intentionally slow (configurable), include a random salt automatically (preventing rainbow table attacks), and produce different hashes for the same password each time. Install: npm install bcryptjs. Hash on registration: const hash = await bcrypt.hash(password, 12) — the 12 is the cost factor (higher = slower = more secure). Verify on login: const valid = await bcrypt.compare(inputPassword, storedHash).

Do Not Use MD5, SHA1, or Plain SHA-256

These are general-purpose hashing algorithms designed to be fast — exactly wrong for passwords. A modern GPU can compute 10 billion MD5 hashes per second. With a leaked database, an attacker can crack common passwords in minutes. Even with a salt, fast algorithms are inadequate. Only use slow algorithms (bcrypt, Argon2, scrypt) for passwords. Use SHA-256/SHA-512 for file integrity checks, API request signing, and other non-password purposes.

Set an Appropriate Cost Factor

bcrypt's cost factor (rounds) determines how slow the hash is. The default of 10 is from 2011 — computers are faster now. Use 12 as a minimum today. At cost 12, hashing takes ~250ms on a modern server — slow enough to make brute force impractical but fast enough for a login endpoint. Test on your server: const start = Date.now(); await bcrypt.hash("test", 12); console.log(Date.now() - start). Aim for 100-300ms.

Implement Account Lockout and Rate Limiting

Even with bcrypt, protect your login endpoint. Implement: rate limiting (max 5 login attempts per 15 minutes per IP using Redis), account lockout (after 10 failed attempts, lock the account for 30 minutes and email the user), and CAPTCHA for repeated failures. Log failed login attempts with IP, timestamp, and username for security auditing. These controls make brute-force attacks impractical even if attackers know your hashing algorithm.

Need Help?

Want this done for you?

Our engineering team handles implementations like this every week. Get a free scoping call — we will tell you exactly what it takes and what it costs.

Book a free call

© 2026 NexWorldTech — Built for Global Dominance.