Right now ANYONE who can reach your API can delete every book with one request. Phase 8 fixes that: librarians log in, routes check who's asking, and roles decide what's allowed. This is the phase where your app becomes trustable.
hash("tiger123") โ "$2b$10$X4kPq..." โ
same input always gives the same output, but there's no way back from output to input. Login then
never compares passwords; it compares hashes: grind what the user typed, check it matches the
stored grind. The server can verify you know the password without ever keeping it.
// bcrypt โ the standard tool (npm install bcrypt)
const hash = await bcrypt.hash(password, 10); // at registration โ store THIS
const ok = await bcrypt.compare(typed, hash); // at login โ true/false
10 = 2ยนโฐ internal rounds; tune it up as
computers speed up). Slow is a feature: irrelevant per login, devastating per billion guesses.
HTTP is stateless โ every request arrives a stranger. Logging in once must somehow cover the next thousand requests. The modern answer: at login, the server issues a signed token (a JWT โ JSON Web Token) that the client shows on every request.
eyJhbGciOi... . eyJ1c2VySWQiOjcsInJvbGUiOi... . 4Hb32k9dQ...
HEADER PAYLOAD SIGNATURE
(algorithm) ({ userId: 7, role: "librarian", (the seal)
exp: 1756600000 })
exp makes it expire like a visitor
badge.
Authorization: Bearer eyJhbG... โ 5) server verifies the signature and trusts the
payload. Steps 4โ5 repeat for every protected request; passwords travel exactly once.
// a preHandler runs BEFORE the route โ a checkpoint at the door
app.post("/loans", { preHandler: [requireLibrarian] }, async (request) => {
// only reached if the checkpoint passed
});
async function requireLibrarian(request, reply) {
const user = await verifyToken(request); // checks the signature
if (!user) return reply.code(401).send({ error: "login required" });
if (user.role !== "librarian") return reply.code(403).send({ error: "librarians only" });
}
| Status | Means | Memory hook |
|---|---|---|
401 Unauthorized | we don't know who you are โ no/bad token | "show me your card" |
403 Forbidden | we know exactly who you are โ and no | "your card doesn't open this door" |
if (!user) return ... โ the same early-return shape as if (!book) return;
from Task 4.4, now guarding a door instead of an array lookup. A preHandler is a guard clause that
Fastify runs for you before the handler. Reusable across every protected route: write the
checkpoint once, list it on each door that needs it.
model User {
id Int @id @default(autoincrement())
username String @unique
passwordHash String // the bcrypt output โ NEVER the password
role String @default("member") // "member" | "librarian"
}
The Library's permission map โ decided in the preHandlers:
| Action | Member | Librarian |
|---|---|---|
| browse/search books | โ | โ |
| see their own loans | โ | โ |
| issue / return books | โ | โ |
| add / delete books | โ | โ |
// after login: keep the token (Zustand store), attach it on every call
fetch("http://localhost:3000/loans", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ bookId, memberName }),
});
| Step | What gets built |
|---|---|
| 1. User model + register | Prisma migration; bcrypt.hash on the way in |
| 2. POST /login | bcrypt.compare โ sign and return a JWT |
| 3. Checkpoints | verify-token preHandler; then the role check |
| 4. Protect the write routes | issue/return/add/delete require librarian |
| 5. React login screen | form โ token in store โ Authorization header everywhere |
| 6. Milestone | only logged-in librarians can issue books โ verified by trying without a token (401) and as a member (403) |
| Idea | One line to remember |
|---|---|
| Authentication vs authorization | who you are vs what you may do |
| Password storage | never โ store a bcrypt hash; compare grinds, not secrets |
| Salt + slow | same password โ same hash; slow per login, fatal per billion guesses |
| JWT | readable ID card with a tamper-proof seal; server stores nothing |
| 401 vs 403 | no card vs wrong card |
| preHandler | your guard clause, standing at the door before the handler |
| Golden rule | the server is the lock; the UI only hides doorknobs |
โ Prev: Tailwind + Zustand ยท Back to contents ยท Next: Desktop & Mobile โ