diff --git a/courses/backend/node/module-materials/examples/auth-jwt.js b/courses/backend/node/module-materials/examples/auth-jwt.js new file mode 100644 index 00000000..17cf9374 --- /dev/null +++ b/courses/backend/node/module-materials/examples/auth-jwt.js @@ -0,0 +1,64 @@ +import express from "express"; +import bcrypt from "bcrypt"; +import jwt from "jsonwebtoken"; + +const JWT_SECRET = process.env.JWT_SECRET || "development-secret"; + +// Example in-memory "database" for teaching purposes only +const users = [ + { + id: 1, + username: "alice", + // bcrypt.hashSync("password123", 10) + password_hash: "$2b$10$exampleexampleexampleexampleexampleexa", + }, +]; + +function getUserByUsername(username) { + return users.find((user) => user.username === username) ?? null; +} + +const app = express(); +app.use(express.json()); + +app.post("/login", async (req, res) => { + const { username, password } = req.body; + + const user = getUserByUsername(username); + if (!user) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + const isMatch = await bcrypt.compare(password, user.password_hash); + if (!isMatch) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: "1h" }); + res.json({ token }); +}); + +function requireJwtAuth(req, res, next) { + const authHeader = req.headers.authorization; + const token = authHeader?.split(" ")[1]; + if (!token) { + return res.status(401).json({ error: "No token provided" }); + } + + try { + const decoded = jwt.verify(token, JWT_SECRET); + req.user = { id: decoded.userId }; + next(); + } catch (err) { + return res.status(401).json({ error: "Invalid or expired token" }); + } +} + +app.get("/protected", requireJwtAuth, (req, res) => { + res.json({ data: "Top secret snippets", userId: req.user.id }); +}); + +app.listen(3001, () => { + console.log("> Ready on http://localhost:3001 (JWT auth example)"); +}); + diff --git a/courses/backend/node/module-materials/examples/auth-login-bcrypt.js b/courses/backend/node/module-materials/examples/auth-login-bcrypt.js new file mode 100644 index 00000000..6c582347 --- /dev/null +++ b/courses/backend/node/module-materials/examples/auth-login-bcrypt.js @@ -0,0 +1,40 @@ +import express from "express"; +import bcrypt from "bcrypt"; + +// Example in-memory "database" for teaching purposes only +const users = [ + { + id: 1, + username: "alice", + // bcrypt.hashSync("password123", 10) + password_hash: "$2b$10$exampleexampleexampleexampleexampleexa", + }, +]; + +function getUserByUsername(username) { + return users.find((user) => user.username === username) ?? null; +} + +const app = express(); +app.use(express.json()); + +app.post("/login", async (req, res) => { + const { username, password } = req.body; + + const user = getUserByUsername(username); + if (!user) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + const isMatch = await bcrypt.compare(password, user.password_hash); + if (!isMatch) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + res.json({ message: "Login successful", userId: user.id }); +}); + +app.listen(3000, () => { + console.log("> Ready on http://localhost:3000 (bcrypt login example)"); +}); + diff --git a/courses/backend/node/module-materials/examples/auth-sessions.js b/courses/backend/node/module-materials/examples/auth-sessions.js new file mode 100644 index 00000000..c48f429e --- /dev/null +++ b/courses/backend/node/module-materials/examples/auth-sessions.js @@ -0,0 +1,60 @@ +import express from "express"; +import session from "express-session"; + +// Example in-memory "database" for teaching purposes only +const users = [ + { + id: 1, + username: "alice", + password: "password123", + }, +]; + +function getUserByUsername(username) { + return users.find((user) => user.username === username) ?? null; +} + +const app = express(); +app.use(express.json()); + +app.use( + session({ + secret: process.env.SESSION_SECRET || "development-session-secret", + resave: false, + saveUninitialized: false, + }), +); + +app.post("/login-session", (req, res) => { + const { username, password } = req.body; + + const user = getUserByUsername(username); + if (!user || user.password !== password) { + return res.status(401).json({ error: "Invalid credentials" }); + } + + req.session.userId = user.id; + res.json({ message: "Logged in with session" }); +}); + +function requireSessionAuth(req, res, next) { + if (req.session.userId) { + return next(); + } + res.status(401).json({ error: "Not authenticated" }); +} + +app.get("/protected-session", requireSessionAuth, (req, res) => { + res.json({ data: "Session-protected snippets", userId: req.session.userId }); +}); + +app.post("/logout-session", (req, res) => { + req.session.destroy(() => { + res.json({ message: "Logged out" }); + }); +}); + +app.listen(3002, () => { + console.log("> Ready on http://localhost:3002 (session auth example)"); +}); + diff --git a/courses/backend/node/week3/README.md b/courses/backend/node/week3/README.md new file mode 100644 index 00000000..c8db41d3 --- /dev/null +++ b/courses/backend/node/week3/README.md @@ -0,0 +1,21 @@ + +# Node (Week 3) – API Security & Authentication + +In this session we will focus on securing our existing Snippets API. We will explore different ways of authenticating users and protecting API endpoints, and compare their trade-offs so you can choose the right approach for different scenarios. + +## Contents + +- [Preparation](./preparation.md) +- [Session Plan](./session-plan.md) (for mentors) +- [Assignment](./assignment.md) + +## Session Learning goals + +By the end of this session, you will be able to: + +- [ ] Explain why storing plaintext passwords is insecure and how hashing (e.g. with bcrypt) improves security. +- [ ] Implement a basic login flow for the Snippets API using securely stored passwords. +- [ ] Protect Snippets API endpoints using JWT-based stateless authentication. +- [ ] Protect Snippets API endpoints using session-based authentication with cookies. +- [ ] Describe when to use database-stored tokens and API keys, and understand their trade-offs. +- [ ] Compare the strengths and weaknesses of credentials-only, DB tokens, JWT, sessions, and API keys for different use cases. diff --git a/courses/backend/node/week3/assignment.md b/courses/backend/node/week3/assignment.md new file mode 100644 index 00000000..ec31e5be --- /dev/null +++ b/courses/backend/node/week3/assignment.md @@ -0,0 +1,159 @@ +# Assignment + +In this assignment, you will extend the **Snippets API** to support multiple authentication mechanisms and reflect on their trade-offs. + +You will: + +- Solidify the secure password and login flow you implemented in class. +- Add database-stored tokens on top of the existing login logic. +- Deepen either your JWT or session-based authentication (or both, if you have time). +- Add a simple API-key-protected machine-style endpoint. + +## Setup + +1. Go to/create a `node/week3` directory in your `hyf-assignment` repo. +2. Copy or link your existing Snippets API code from week 2 (or the provided starter), so that you have: + - A working database connection and Knex setup. + - Existing snippets-related endpoints under `/api/snippets`. +3. Make sure your database contains the required tables for the Snippets API and that you can run it locally without errors. +4. Add (or confirm) a `users` table to your database with at least: + - `id` + - `username` (unique) + - `password_hash` +5. Ensure you have environment variables (or config values) for: + - `JWT_SECRET` + - `SESSION_SECRET` + - `API_KEY` (for the machine-style endpoint). + +> If you are missing any of these pieces, revisit the week 3 session materials and implement the in-class steps first before moving on. + +--- + +## Part 1 – Solidify in-class implementation + +Start from the state of your Snippets API at the end of the week 3 session. + +Your goals: + +- Make sure the secure `/login` endpoint using bcrypt is correctly implemented. +- Make sure at least one auth mechanism (JWT or sessions) from the session is working reliably. + +### Requirements + +- Verify that: + - `users` table exists and contains at least one user with a hashed password. + - `/login`: + - Looks up the user by username (or email). + - Uses `bcrypt.compare` to check the password. + - Returns appropriate HTTP status codes on success (`200`) and failure (`401`). + - If using **JWT**: + - The login route issues a JWT signed with `JWT_SECRET`. + - A middleware verifies the token and attaches user info to `req.user`. + - If using **sessions**: + - Session middleware is configured with `SESSION_SECRET`. + - Login sets `req.session.userId`. + - A middleware checks for `req.session.userId` and rejects unauthenticated requests. +- Add or update error handling so that: + - You do not leak sensitive details in responses. + - You log enough server-side information to debug issues. + +Document briefly (e.g. in comments or a short `AUTH_NOTES.md`) which auth mechanism you have working at this stage. + +--- + +## Part 2 – Database-stored tokens + +Next, add **database-stored tokens** to your Snippets API, in addition to your existing mechanism. + +### Requirements + +1. Create a `tokens` table with at least: + - `id` (primary key) + - `user_id` (foreign key to `users.id`) + - `token` (unique string) + - `created_at` (timestamp) + - `expires_at` (timestamp, optional) +2. Implement a `/login-token` route that: + - Reuses your secure username/password check. + - Generates a random token (for example using `crypto.randomBytes`). + - Stores the token and user ID in the `tokens` table. + - Returns the token to the client in JSON. +3. Implement `authToken` middleware that: + - Reads the `Authorization` header (`Bearer `). + - Looks up the token in the `tokens` table. + - (Optionally) checks `expires_at`. + - Attaches the user to `req.user` or returns `401` on failure. +4. Protect at least **two existing Snippets API endpoints** using `authToken`, for example: + - `POST /api/snippets` + - `DELETE /api/snippets/:id` +5. Implement a `/logout-token` route that: + - Deletes or invalidates the token record from the `tokens` table. + +--- + +## Part 3 – Deepen JWT or sessions (or both) + +Choose **at least one** mechanism to deepen: **JWT** or **sessions**. + +### Option A – Deepen JWT + +If you choose JWT, extend your implementation by: + +- Improving error handling (e.g. distinguish between missing, invalid, and expired tokens in a safe way). +- Adding at least one of: + - Short-lived access tokens plus a simple refresh token flow. + - Role-based checks in middleware (e.g. only certain users can delete snippets). + +### Option B – Deepen sessions + +If you choose sessions, extend your implementation by: + +- Improving session configuration (cookie options, lifetime, etc.). +- Ensuring proper session destruction on logout. +- Considering what would be needed to run the app on multiple servers (e.g. shared session storage) and documenting your thoughts. + +### Requirements + +- Clearly document (in code comments or `AUTH_NOTES.md`) how to: + - Obtain credentials and log in. + - Use the improved mechanism (which headers/cookies are expected). + - Log out or otherwise invalidate access. + +You may work on both JWT and sessions if you have time, but it is acceptable to focus deeply on one. + +--- + +## Part 4 – API-key-protected machine endpoint + +Finally, add a simple **API-key-protected endpoint** intended for machine-to-machine use. + +### Requirements + +1. Choose or create a route that makes sense for a machine client, for example: + - `GET /api/metrics` + - `GET /api/health` + - `GET /api/snippets/export` +2. Introduce an environment variable such as `API_KEY`. +3. Implement middleware (e.g. `requireApiKey`) that: + - Reads the `x-api-key` header. + - Compares it with `API_KEY`. + - Returns `401` on missing/incorrect keys. +4. Protect your machine-style route with this middleware. + +### Optional stretch: basic rate limiting + +If you have time, add a very simple in-memory rate-limiting mechanism (for example, per API key) and document the limitations of such an approach. + +--- + +## Reflection + +Add a short reflection section to your repository (for example in `AUTH_NOTES.md` or at the bottom of this file) where you answer the following questions in a few sentences or bullet points each: + +1. Which auth mechanism would you choose for: + - A SPA web app with many users? + - A microservice-to-microservice communication scenario? + - An internal admin tool used by a small team? +2. Why would you **not** use the other mechanisms in those scenarios? +3. What is one security improvement you would like to make next if you had more time? + diff --git a/courses/backend/node/week3/preparation.md b/courses/backend/node/week3/preparation.md new file mode 100644 index 00000000..757fbcbc --- /dev/null +++ b/courses/backend/node/week3/preparation.md @@ -0,0 +1,15 @@ +# Preparation + +//TODO: link resources + +- Make sure you can run the **Snippets API** locally // TODO: after steamlining week 1 and 2 +- Read a short introduction to **password hashing and salting** (for example, an article explaining why plaintext passwords are insecure and how bcrypt works) // TODO +- Read a high-level overview of **JWT (JSON Web Tokens)** and how they are used for stateless authentication // TODO +- Read a brief introduction to **cookies and sessions** in web applications // TODO. + +## Optional Resources + +For more research, you can explore the following resources: + +- OWASP cheatsheets on authentication and session management (for a deeper security perspective). //TODO +- A more in-depth article or video about JWT best practices (token lifetimes, refresh tokens, common pitfalls). //TODO diff --git a/courses/backend/node/week3/session-materials/10-auth-db-credentials.md b/courses/backend/node/week3/session-materials/10-auth-db-credentials.md new file mode 100644 index 00000000..10b7054b --- /dev/null +++ b/courses/backend/node/week3/session-materials/10-auth-db-credentials.md @@ -0,0 +1,46 @@ +# Secure passwords and basic login (Snippets API) + +In this part of the session, you will add **secure password storage** and a **basic login endpoint** to the Snippets API. + +We will: + +- Create a `users` table in the database. +- Hash passwords using `bcrypt`. +- Implement a `/login` endpoint that validates a user’s credentials. + +## 1. Database: users table + +Add a `users` table to the same database that the Snippets API uses. A minimal schema could look like: + +- `id` (primary key) +- `username` (unique) +- `password_hash` (string, to store the hashed password) + +Seed at least one user with a hashed password (for example using a script or a small Node program that calls `bcrypt.hash` and inserts the row). + +## 2. Install bcrypt + +Install `bcrypt` (or `bcryptjs`) in the Snippets API project and import it in your auth route module. + +## 3. Implement /login + +Create a route (for example in `routes/auth.js`) that: + +1. Reads `username` and `password` from the request body. +2. Looks up the user by username in the database. +3. Uses `bcrypt.compare` to compare the provided password with the stored `password_hash`. +4. Returns: + - `401 Unauthorized` with a generic error message on failure. + - `200 OK` (or `201`) with a small success payload on success. + +You do **not** need to generate tokens here yet – this is just about secure credential checking. + +## 4. Suggested exercises + +- Add at least one extra user to the database and test logging in as both. +- Think about: + - What error messages you send back (security vs usability). + - Whether you log failed login attempts (but avoid logging raw passwords). + +In the next parts, you’ll build **JWT** and **session-based** authentication on top of this secure login. + diff --git a/courses/backend/node/week3/session-materials/11-auth-db-tokens.md b/courses/backend/node/week3/session-materials/11-auth-db-tokens.md new file mode 100644 index 00000000..f5726eda --- /dev/null +++ b/courses/backend/node/week3/session-materials/11-auth-db-tokens.md @@ -0,0 +1,50 @@ +# Database-stored tokens (concept and extension) + +In this part, you will explore **database-stored tokens** as another way of implementing authentication in the Snippets API. + +This is mainly for **conceptual understanding in class** and for **extension work in the assignment**. + +We will: + +- Introduce a `tokens` table. +- Issue random tokens on login. +- Validate tokens on each request. + +## 1. Database: tokens table + +Add a `tokens` table to the Snippets database, for example with columns: + +- `id` (primary key) +- `user_id` (foreign key to `users.id`) +- `token` (string, unique) +- `created_at` (timestamp) +- `expires_at` (timestamp, optional) + +## 2. Issue a token on login + +Extend login (or create a separate `/login-token` route) so that: + +1. You first verify the username and password using your secure logic. +2. Generate a random token value (for example using `crypto.randomBytes`). +3. Insert a new row into the `tokens` table with the user ID and token. +4. Return the token to the client (e.g. `{ "token": "" }`). + +## 3. Token auth middleware + +Create middleware (e.g. `requireTokenAuth`) that: + +1. Reads the `Authorization` header, expecting something like `Bearer `. +2. Looks up the token in the `tokens` table. +3. (Optionally) checks for expiration. +4. Attaches the corresponding user to `req.user`, or returns `401` if the token is not valid. + +## 4. Suggested exercises + +- Protect one or more Snippets API endpoints with your token-based middleware. +- Implement a `/logout-token` endpoint that deletes the token from the database. +- Consider: + - How many concurrent tokens you allow per user. + - How you might clean up old or expired tokens. + +You will build on this concept further in the assignment. + diff --git a/courses/backend/node/week3/session-materials/12-auth-jwt.md b/courses/backend/node/week3/session-materials/12-auth-jwt.md new file mode 100644 index 00000000..5cf4da52 --- /dev/null +++ b/courses/backend/node/week3/session-materials/12-auth-jwt.md @@ -0,0 +1,50 @@ +# Stateless authentication with JWT (Snippets API) + +In this part, you will add **JWT-based authentication** to the Snippets API on top of the secure `/login` flow you built earlier. + +We will: + +- Issue a JWT after a successful login. +- Validate the JWT on protected routes. +- Use it to guard important Snippets API endpoints. + +## 1. Install and configure jsonwebtoken + +- Install the `jsonwebtoken` package in your Snippets API project. +- Decide on a `JWT_SECRET` value (for local development you can use an environment variable or a hardcoded string). + +## 2. Extend /login to issue a JWT + +Re-use your secure `/login` route: + +- After verifying the password with bcrypt, generate a JWT with a payload like `{ userId: user.id }`. +- Set a reasonable expiration time (for example 1 hour). +- Return the token in the JSON response. + +## 3. Create JWT auth middleware + +Create a middleware function (for example in `middleware/auth-jwt.js`) that: + +1. Reads the `Authorization` header (`Bearer `). +2. Verifies the token using `jwt.verify`. +3. Attaches the decoded user information to `req.user`. +4. Sends a `401` response if the token is missing, invalid, or expired. + +## 4. Protect Snippets API endpoints + +Choose at least two endpoints in your Snippets API (for example): + +- `POST /api/snippets` +- `DELETE /api/snippets/:id` + +Wrap these handlers with your JWT auth middleware so that: + +- Requests without a valid token are rejected with `401` and a suitable JSON error. +- Requests with a valid token are allowed to proceed. + +## 5. Suggested exercises + +- Experiment with different expiration times and observe what happens when a token expires. +- Try to tamper with the token (e.g. change the payload in Postman) and see how your middleware responds. +- Add some basic logging around authentication failures (but make sure not to log secrets or full tokens). + diff --git a/courses/backend/node/week3/session-materials/13-auth-sessions.md b/courses/backend/node/week3/session-materials/13-auth-sessions.md new file mode 100644 index 00000000..c9ae4c43 --- /dev/null +++ b/courses/backend/node/week3/session-materials/13-auth-sessions.md @@ -0,0 +1,50 @@ +# Session-based authentication (Snippets API) + +In this part, you will add **session-based authentication** to the Snippets API and compare it to your JWT-based approach. + +We will: + +- Configure `express-session`. +- Implement login and logout using sessions. +- Protect at least one Snippets API route with session-based middleware. + +## 1. Configure express-session + +- Install `express-session` in your project if it is not already installed. +- In your main application file (e.g. `app.js`), add the session middleware with: + - A `secret` value (for local development you can keep it simple). + - Reasonable `resave` and `saveUninitialized` settings. + +## 2. Implement login with sessions + +Create a route (for example in `routes/auth-session.js`) that: + +1. Reads `username` and `password` from the request body. +2. Looks up the user and verifies the password using bcrypt (re-using your secure users table). +3. Sets `req.session.userId` when the login is successful. +4. Returns a small success payload (e.g. `{ "message": "Logged in with session" }`). + +## 3. Protect routes with session middleware + +Create a middleware function (for example `requireSessionAuth`) that: + +1. Checks if `req.session.userId` is set. +2. Calls `next()` if it is. +3. Sends a `401` response with a short JSON error if it is not. + +Use this middleware to protect at least one Snippets API route (for example, a route that creates or deletes a snippet). + +## 4. Implement logout + +Add a `/logout-session` route that: + +- Destroys the current session (e.g. via `req.session.destroy`). +- Returns a simple JSON response to confirm logout. + +## 5. Suggested exercises + +- Compare your session-based solution with your JWT-based solution: + - What changes in the client’s behaviour? + - How does each solution handle revocation? + - What would you need to consider when scaling beyond a single server instance? + diff --git a/courses/backend/node/week3/session-materials/14-auth-api-keys.md b/courses/backend/node/week3/session-materials/14-auth-api-keys.md new file mode 100644 index 00000000..0280b4cb --- /dev/null +++ b/courses/backend/node/week3/session-materials/14-auth-api-keys.md @@ -0,0 +1,31 @@ +# API keys and wrap-up + +In this final part, you will look at **API keys** as a simple mechanism for **machine-to-machine authentication**, and you will review the authentication methods covered in the session. + +## 1. API keys: concept + +- API keys are usually long, random strings given to other services or scripts. +- The client includes the key with each request (for example in an `x-api-key` header). +- The server validates the key and decides whether to allow the request. +- API keys are convenient for machines, but not ideal as the only method of authenticating human users. + +## 2. Adding an API-key-protected route (Snippets API) + +As an example, you can: + +- Introduce an environment variable like `API_KEY`. +- Create a middleware (e.g. `requireApiKey`) that: + - Reads the `x-api-key` header. + - Compares it with the configured key. + - Returns `401` if it is missing or incorrect. +- Use this middleware on a “machine-style” endpoint, such as: + - A `/metrics` or `/health` endpoint. + - A bulk export route for snippets data. + +## 3. Optional rate limiting idea + +To prevent abuse, you can sketch (or implement) a very simple rate limiting strategy: + +- Use an in-memory object to count requests per API key. +- Deny further requests after a certain number within a short time window. +- Discuss why a production-grade solution would need shared storage and more robust logic. diff --git a/courses/backend/node/week3/session-plan.md b/courses/backend/node/week3/session-plan.md new file mode 100644 index 00000000..09c5dc15 --- /dev/null +++ b/courses/backend/node/week3/session-plan.md @@ -0,0 +1,155 @@ +# Session plan + +## Session outline + +- Secure passwords & basic login using the Snippets API (≈40 min) +- Stateless auth with JWT on the Snippets API (≈40 min) +- Session-based auth on the Snippets API (≈40 min) +- Comparison of auth methods, brief look at DB tokens & API keys, and wrap-up (≈30–35 min) + +## Secure passwords & basic login (Snippets API) + +**Goal**: Implement secure password storage and a basic login flow for the Snippets API using bcrypt. + +### Lecture & live coding (≈10 min) + +- Concept: why plaintext passwords are insecure. +- Introduce hashing and salting with bcrypt. +- Show how a minimal `users` table for the Snippets DB might look. +- Walk through the happy path for `/login`: + - Look up user by username/email. + - Compare plaintext password with stored hash using bcrypt. + +### Implementation (live coding) + +- Sketch the table and login route, using the example in + [module-materials/examples/auth-login-bcrypt.js](../../module-materials/examples/auth-login-bcrypt.js). + +_See [Secure passwords and basic login](./session-materials/10-auth-db-credentials.md) for a more detailed walkthrough._ + +### Exercise (15–20 min) + +- Add a `users` table to the Snippets DB and seed at least one user with a hashed password. +- Implement `/login` using bcrypt in your own copy of the Snippets API. +- Use Postman to test successful and failing login attempts. + +### Do it together (5–10 min) + +- Show one working solution. +- Discuss common mistakes (e.g. sending too much error detail, forgetting to hash passwords). + +--- + +## Stateless auth with JWT + +**Goal**: Learn stateless auth with JWT on top of the secure login flow, and protect key Snippets API endpoints. + +### Lecture & live coding (≈10 min) + +- Concept: self-contained tokens with claims; no DB lookup needed per request. +- Trade-offs: fast and scalable, but harder to revoke. +- Show the high-level flow: + - After successful bcrypt-based login, issue a JWT. + - Client includes the token in the `Authorization` header. + - Middleware verifies the token and attaches user info to `req.user`. + +### Implementation (live coding) + +- Use the JWT login and middleware flow from + [module-materials/examples/auth-jwt.js](../../module-materials/examples/auth-jwt.js) as a reference while coding in the Snippets API. + +_See [Stateless authentication with JWT](./session-materials/12-auth-jwt.md) for detailed steps and examples._ + +### Exercise (15–20 min) + +- Protect at least two Snippets API endpoints (e.g. `POST /api/snippets`, `DELETE /api/snippets/:id`) using JWT middleware. +- Add token expiration and handle expired tokens gracefully in responses. +- Try to tamper with the token payload and see what happens. + +### Do it together (5–10 min) + +- Run through a full Postman flow together. +- Discuss where to store JWTs on the client (headers vs cookies) in different types of apps. + +--- + +## Session-based authentication + +**Goal**: Implement session-based authentication using cookies and compare it to JWT for the Snippets API. + +### Lecture & live coding (≈10 min) + +- Concept: server-side sessions, session IDs in cookies, and typical use cases. +- Contrast with JWT: stateful vs stateless, revocation, and infrastructure needs. + +### Implementation (live coding) + +- Use the session-based login and middleware flow from + [module-materials/examples/auth-sessions.js](../../module-materials/examples/auth-sessions.js) as a reference while integrating sessions into the Snippets API. + +_See [Session-based authentication](./session-materials/13-auth-sessions.md) for detailed guidance._ + +### Exercise (15–20 min) + +- Protect at least one Snippets API route using session-based middleware. +- Implement logout and verify that access is denied after logging out. + +### Do it together (5–10 min) + +- Compare the experience of working with sessions vs JWT. +- Discuss scaling concerns (sticky sessions, shared stores) at a high level. + +--- + +## Comparison, DB tokens & API keys overview, and wrap-up + +**Goal**: Compare the different auth approaches, introduce DB-stored tokens and API keys conceptually, and connect to the assignment. + +### Short lecture & discussion (10–15 min) + +- Revisit the methods you have seen: + - Secure passwords (with bcrypt) and basic login. + - JWT-based stateless auth. + - Session-based auth. +- Introduce **database-stored tokens**: + - Tokens stored in a `tokens` table, lookup on each request. + - Easier revocation, but requires a DB call every time. +- Introduce **API keys**: + - Simple secret keys for machine-to-machine communication. + - Not ideal as the only method for user authentication. +- Show a comparison table summarising trade-offs: + +| Method | DB Lookup | Revocable | Scalable | Typical use case | +| ------------------ | --------- | --------- | -------- | ----------------------- | +| Secure credentials | Yes | No | No | Legacy / simple systems | +| Database tokens | Yes | Yes | No | Small apps | +| JWT | No | Not easy | Yes | SPAs, mobile apps | +| Sessions | Yes | Yes | Depends | Traditional web apps | +| API keys | No | Not easy | Yes | Machine-to-machine | + +_See [Database-stored tokens](./session-materials/11-auth-db-tokens.md) and [API keys](./session-materials/14-auth-api-keys.md) for additional details and examples._ + +## Comparing auth methods + +Review the methods from this week: + +- Secure credentials (hashed passwords + login). +- Database-stored tokens. +- JWT. +- Sessions. +- API keys. + +Discuss trade-offs in terms of: + +- Performance (DB lookups vs stateless verification). +- Revocation. +- Complexity on the client and server. +- Fit for different scenarios (SPAs, mobile apps, internal tools, service-to-service communication). + +### Final wrap-up + +- Reiterate best practices: + - Always use HTTPS. //TODO: WHY and how it's connected + - Always hash passwords (bcrypt or similar). + - Store tokens securely (e.g. HttpOnly cookies for web clients). + - Prefer short-lived tokens and consider refresh tokens where appropriate.