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
validateRegistrationmiddleware 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.