Securing Passwords and Authentication Best Practices

Securing Passwords and Authentication Best Practices

Last updated: 3/7/2025

1 hour
Medium

Securing Passwords and Authentication Best Practices

🌍 Introduction

In the previous lessons, we implemented user authentication and authorization. However, authentication systems are common targets for attacks.
To protect user accounts, we must secure passwords and authentication mechanisms.

In this lesson, you'll learn: βœ… Best practices for password security.
βœ… How to prevent brute force attacks.
βœ… How to protect JWT tokens from being stolen.
βœ… Other security measures like rate limiting and multi-factor authentication (MFA).


πŸ“Œ 1. Best Practices for Password Security

βœ… 1️⃣ Always Hash Passwords Before Storing

Storing raw passwords in the database is extremely dangerous. If a database leak occurs, all passwords would be exposed.

πŸ”Ή Solution: Use bcrypt for hashing passwords.
πŸ”Ή Why? bcrypt applies salt (random data) to the hash, preventing rainbow table attacks.

βœ” Good Example (Hashing Passwords in Node.js)

const bcrypt = require("bcryptjs"); const hashPassword = async (password) => { const salt = await bcrypt.genSalt(10); return await bcrypt.hash(password, salt); };

❌ Bad Example (Storing Plain Text Passwords)

const user = new User({ email, password: req.body.password }); // ❌ No Hashing

βœ… 2️⃣ Enforce Strong Password Policies

Weak passwords make brute-force attacks easier.

βœ” Good Password Policy

  • Minimum 8 characters
  • At least one uppercase letter
  • At least one number
  • At least one special character

πŸ”Ή Solution: Use a validation library like Zod or Joi:

const z = require("zod"); const passwordSchema = z.string() .min(8, "Password must be at least 8 characters") .regex(/[A-Z]/, "Must include an uppercase letter") .regex(/[0-9]/, "Must include a number") .regex(/[^A-Za-z0-9]/, "Must include a special character");

βœ… 3️⃣ Implement Rate Limiting to Prevent Brute Force Attacks

Attackers may attempt multiple login requests to guess passwords.

πŸ”Ή Solution: Use Express Rate Limit Middleware

const rateLimit = require("express-rate-limit"); const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // Limit to 5 login attempts per IP message: { error: "Too many login attempts. Please try again later." } }); app.use("/auth/login", loginLimiter);

βœ” Blocks excessive login attempts and prevents brute force attacks.


πŸ“Œ 2. Protecting JWT Tokens

βœ… 4️⃣ Use Secure HTTP-Only Cookies Instead of Local Storage

Many APIs store JWT tokens in localStorage, but this is unsafe due to XSS attacks.

πŸ”Ή Solution: Store JWTs in HTTP-only cookies (safer)

res.cookie("token", token, { httpOnly: true, secure: true, // Only send over HTTPS sameSite: "Strict" });

βœ… Why? βœ” Prevents cross-site scripting (XSS) attacks.
βœ” Ensures the token is only accessible by the server.


βœ… 5️⃣ Implement Token Expiry and Refresh Tokens

πŸ”Ή Why? If a JWT token is stolen, it should not remain valid forever.
πŸ”Ή Solution:

  • Set an expiry time for access tokens (expiresIn: "1h")
  • Use refresh tokens to issue new access tokens without requiring login.

Example of issuing a short-lived access token and a refresh token:

const accessToken = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "15m" }); const refreshToken = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "7d" });

βœ” Short-lived tokens reduce risk if stolen.
βœ” Refresh tokens allow re-authentication without needing passwords.


πŸ“Œ 3. Implementing Multi-Factor Authentication (MFA)

Even with strong passwords, accounts can still be compromised. MFA adds an extra layer of security.

βœ… 6️⃣ Enable Two-Factor Authentication (2FA)

πŸ”Ή Users must verify their identity using:
βœ” A password (something they know)
βœ” A one-time code from Google Authenticator or SMS (something they have)

πŸ”Ή Solution: Use an MFA provider like Authy, Google Authenticator, or Twilio Verify.

Example: 2FA Flow

  1. User enters their email & password.
  2. Server sends a one-time code (OTP) to their phone.
  3. User enters the OTP to complete login.

Example: Generating a 2FA Code

const speakeasy = require("speakeasy"); // Generate a secret for the user const secret = speakeasy.generateSecret({ length: 20 }); // Generate a one-time token const token = speakeasy.totp({ secret: secret.base32, encoding: "base32" }); console.log("2FA Code:", token);

βœ… Why?
βœ” Even if a password is stolen, hackers cannot log in without the OTP.


πŸ“Œ 4. Additional Security Best Practices

βœ” Use HTTPS – Prevents man-in-the-middle attacks.
βœ” Log failed login attempts – Detects potential threats.
βœ” Monitor API activity – Identify suspicious behavior.


🎯 Summary

βœ… Passwords must always be hashed before storing them.
βœ… Implement rate limiting to prevent brute force attacks.
βœ… Use HTTP-only cookies for storing JWT tokens instead of localStorage.
βœ… Implement MFA (Two-Factor Authentication) for extra security.
βœ… Enforce token expiration & refresh tokens to improve security.


βœ… Next Lesson: OAuth and Third-Party Authentication (Google, GitHub, etc.)

In the next lesson, we’ll implement OAuth authentication using Google and GitHub logins! πŸš€