Identify your API users. Hash passwords, issue and verify JSON Web Tokens (JWT), protect routes with middleware, and understand where Passport.js fits.
Why: never store passwords as plain text. Hashing turns a password into a scrambled string that cannot be reversed; you store the hash and compare against it at login. bcrypt is the standard tool. (Salting — adding randomness — is built in.)
$ pnpm add bcrypt// app.js
import bcrypt from 'bcrypt'
// when a user signs up
const hash = await bcrypt.hash('mypassword', 10)
// when they log in
const ok = await bcrypt.compare('mypassword', hash)
console.log(ok) // trueWhy: after login, the server gives the user a token — a signed string proving who they are. On later requests the user sends it back instead of logging in again. "Signed" means the server can detect tampering. jsonwebtoken creates and verifies them.
$ pnpm add jsonwebtoken// .env
SECRET = 123456// app.js
import jwt from "jsonwebtoken";
import "dotenv/config";
const SECRET = process.env.JWT_SECRET;
// create a token at login (expires in 1 hour)
const token = jwt.sign({ userId: 42 }, SECRET, { expiresIn: "1h" });
console.log(token);
// verify it on later requests
const payload = jwt.verify(token, SECRET);
console.log(payload.userId); // 42Why: to guard a route, add middleware that reads the token from the Authorization header, verifies it, and either lets the request through (next()) or rejects it with 401 Unauthorized.
import jwt from "jsonwebtoken";
import "dotenv/config";
import express from "express";
const app = express();
function requireAuth(req, res, next) {
const header = req.headers.authorization || "";
const token = header.replace("Bearer ", "");
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next(); // allowed
} catch {
res.status(401).json({ error: "Not authenticated" });
}
}
app.get("/profile", requireAuth, (req, res) => {
res.json({ userId: req.user.userId });
});