The REST API lesson mentioned that not just anyone should be able to delete a product — this lesson is how that's actually enforced: hashing passwords safely, and issuing a token that proves who's making a request on every request afterward.
bcrypt hashes a password with a random salt baked in — the same password hashed twice produces two different results, which is exactly what stops a stolen password database from being searchable with a precomputed table of common passwords.
import bcrypt from 'bcrypt'
// When a user signs up
const passwordHash = await bcrypt.hash(req.body.password, 10)
// Store passwordHash in the database — never the plain password
// When a user logs in
const isCorrect = await bcrypt.compare(req.body.password, user.passwordHash)After a successful login, the server issues a JSON Web Token — a signed string the client sends back on every future request (usually in an Authorization header) instead of logging in again each time.
import jwt from 'jsonwebtoken'
// After verifying the password is correct
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
)
res.json({ token })function requireAuth(req, res, next) {
const authHeader = req.headers.authorization
const token = authHeader?.split(' ')[1] // "Bearer <token>"
if (!token) {
return res.status(401).json({ error: 'No token provided' })
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET)
req.userId = payload.userId
next()
} catch {
res.status(401).json({ error: 'Invalid or expired token' })
}
}
app.delete('/products/:id', requireAuth, (req, res) => {
// req.userId is now available — this route only runs for a valid token
})This is exactly the middleware pattern from the earlier Middleware lesson — requireAuth runs before the route handler, and can block the request entirely before it reaches the actual logic.
| Piece | Purpose |
|---|---|
| bcrypt.hash() | Turns a plain password into a safe, salted hash for storage |
| bcrypt.compare() | Checks a login attempt's password against the stored hash |
| jwt.sign() | Issues a signed token after a successful login |
| jwt.verify() | Confirms a token is genuine and not expired, on every protected request |
The one secret this whole system depends on
JWT_SECRET must be a long, random, private value kept in an environment variable — never committed to the repo. Anyone who obtains it can forge a valid token for any user.