Password Security

Before we store passwords in our database, we need to address a critical security issue: we can never store passwords as plain text. If someone gains access to our database, they would see all user passwords immediately.

Instead, we need to hash (encrypt) passwords using a secure hashing algorithm. We’ll use bcrypt, which is specifically designed for password hashing and includes built-in protection against common attacks.

What bcrypt Does

  • Converts "mypassword123" into something like "$2b$10$N9qo8uLOickgx2ZMRZoMy.ZjRnKZ9..."
  • Even if someone steals our database, they can’t see the actual passwords.
  • Uses “salt” to ensure the same password creates different hashes for different users.

Installing Dependencies

Install bcrypt for secure password hashing:

npm install bcrypt jsonwebtoken
npm install --save-dev @types/bcrypt @types/jsonwebtoken

New auth routes file

We’ll create our auth routes in a new routes/auth.ts file.

Using Authentication Validation from the Previous Lesson

In the previous lesson, we created registerSchema, loginSchema, and their validation middleware functions in middleware/auth-validation.ts. We’ll import and use those in our authentication endpoints along with the User and UserResponse interfaces and the other required imports:

import { Router } from "express";
import bcrypt from "bcrypt";
import { ResultSetHeader } from "mysql2";
import { pool } from "../database";
import {
  validateRegistration,
  validateLogin,
} from "../middleware/auth-validation";
import { User, UserResponse } from "../interfaces";

Creating the Registration Route

Now we’ll add our register endpoint:

const router = Router();

router.post("/register", validateRegistration, async (req, res) => {
  try {
    const { username, email, password } = req.body;

    // Check if user already exists
    const [rows] = await pool.execute(
      "SELECT * FROM users WHERE email = ? OR username = ?",
      [email, username]
    );
    const existingUsers = rows as User[];

    if (existingUsers.length > 0) {
      return res.status(400).json({
        error: "User already exists with that email or username",
      });
    }

    // Hash the password
    const saltRounds = 10;
    const hashedPassword = await bcrypt.hash(password, saltRounds);

    // Create the user in database
    const [result]: [ResultSetHeader, any] = await pool.execute(
      "INSERT INTO users (username, email, password) VALUES (?, ?, ?)",
      [username, email, hashedPassword]
    );

    // Return user info (without password)
    const userResponse: UserResponse = {
      id: result.insertId,
      username,
      email,
    };

    res.status(201).json({
      message: "User registered successfully",
      user: userResponse,
    });
  } catch (error) {
    console.error("Registration error:", error);
    res.status(500).json({
      error: "Failed to register user",
    });
  }
});

export default router;

Understanding the Registration Process

Step-by-step Breakdown

  • Validation middleware: Uses validateRegistration middleware to check input requirements.
  • Duplicate check: Ensures email and username are unique in the database.
  • Password hashing: bcrypt converts plain text password into a secure hash.
  • Database insertion: Stores user with hashed password (never plain text).
  • Response: Returns user info without password for security.

Connect Auth Routes

Before we create the login route, let’s register the authRoutes in index.ts.

// other imports
import authRoutes from "./routes/auth";

// other routes
app.use("/auth", authRoutes);

Testing the Register Endpoint

Test the auth/register endpoint by posting different data to view the success message or validation messages:

POST /auth/register
Content-Type: application/json

{
"username": "john_doe",
"email": "john@example.com",
"password": "B@password111"
}

After successfully creating a user, run SELECT * from users to view the new user with its hashed password.



Repo link

Tags: