From 180788cf03bbf942b1eea30fa2fbbd3737c5d053 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 4 Mar 2026 16:24:59 +0100 Subject: [PATCH 1/3] feat: week 3 scaffolding --- courses/backend/node/week3/README.md | 0 courses/backend/node/week3/assignment.md | 0 courses/backend/node/week3/preparation.md | 0 courses/backend/node/week3/session-plan.md | 273 +++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 courses/backend/node/week3/README.md create mode 100644 courses/backend/node/week3/assignment.md create mode 100644 courses/backend/node/week3/preparation.md create mode 100644 courses/backend/node/week3/session-plan.md diff --git a/courses/backend/node/week3/README.md b/courses/backend/node/week3/README.md new file mode 100644 index 00000000..e69de29b diff --git a/courses/backend/node/week3/assignment.md b/courses/backend/node/week3/assignment.md new file mode 100644 index 00000000..e69de29b diff --git a/courses/backend/node/week3/preparation.md b/courses/backend/node/week3/preparation.md new file mode 100644 index 00000000..e69de29b diff --git a/courses/backend/node/week3/session-plan.md b/courses/backend/node/week3/session-plan.md new file mode 100644 index 00000000..96abc549 --- /dev/null +++ b/courses/backend/node/week3/session-plan.md @@ -0,0 +1,273 @@ +# **Session Plan: API Security & Authentication in Node.js** + +**Duration**: 3.5 hours +**Format**: Lecture + Live Coding + Exercises + +--- + +## **1. Database-Stored Credentials (30 min)** + +**Goal**: Understand basic auth flow, identify security flaws. + +### **Lecture (10 min)** + +- **Concept**: Store username/password in DB, check on login. +- **Security Issues**: Plaintext passwords, no token management. + +### **Implementation (15 min)** + +**Code Snippet**: + +```js +// routes/auth.js +const express = require("express"); +const router = express.Router(); +const db = require("../db"); + +// Login endpoint +router.post("/login", async (req, res) => { + const { username, password } = req.body; + const user = await db.getUserByUsername(username); + if (!user || user.password !== password) { + return res.status(401).send("Invalid credentials"); + } + res.send("Login successful"); +}); + +module.exports = router; +``` + +**Middleware**: + +```js +// middleware/auth.js +function isAuthenticated(req, res, next) { + if (req.session.user) return next(); + res.status(401).send("Unauthorized"); +} +``` + +### **Exercise (5 min)** + +- Add a protected route `/profile` using `isAuthenticated`. +- Test with Postman: send username/password in body. + +**Discussion**: + +- Why is this insecure? +- What if the DB is compromised? + +--- + +## **2. Database-Stored Tokens (45 min)** + +**Goal**: Improve security with tokens, understand token lifecycle. + +### **Lecture (10 min)** + +- **Concept**: Issue a random token on login, store in DB, validate on each request. +- **Pros/Cons**: Revocable, but DB lookup on every request. + +### **Implementation (20 min)** + +**Code Snippet**: + +```js +// routes/auth.js +const crypto = require("crypto"); + +router.post("/login", async (req, res) => { + const { username, password } = req.body; + const user = await db.getUserByUsername(username); + if (!user || user.password !== password) { + return res.status(401).send("Invalid credentials"); + } + const token = crypto.randomBytes(32).toString("hex"); + await db.storeToken(token, user.id); + res.json({ token }); +}); + +// middleware/auth.js +async function isAuthenticated(req, res, next) { + const token = req.headers.authorization?.split(" ")[1]; + if (!token) return res.status(401).send("No token"); + const userId = await db.getUserIdByToken(token); + if (!userId) return res.status(401).send("Invalid token"); + req.userId = userId; + next(); +} +``` + +### **Exercise (10 min)** + +- Implement `/logout` endpoint: delete token from DB. +- Test with Postman: login, access `/profile`, logout, try again. + +**Discussion**: + +- How does this compare to the previous method? +- What if the DB is slow? + +--- + +## **3. JWT (JSON Web Tokens) (45 min)** + +**Goal**: Learn stateless auth, understand JWT structure and risks. + +### **Lecture (10 min)** + +- **Concept**: Self-contained tokens, no DB lookup. +- **Pros/Cons**: Fast, but hard to revoke. + +### **Implementation (20 min)** + +**Code Snippet**: + +```js +// routes/auth.js +const jwt = require("jsonwebtoken"); +const SECRET = "your-secret-key"; + +router.post("/login", async (req, res) => { + const { username, password } = req.body; + const user = await db.getUserByUsername(username); + if (!user || user.password !== password) { + return res.status(401).send("Invalid credentials"); + } + const token = jwt.sign({ userId: user.id }, SECRET, { expiresIn: "1h" }); + res.json({ token }); +}); + +// middleware/auth.js +function isAuthenticated(req, res, next) { + const token = req.headers.authorization?.split(" ")[1]; + if (!token) return res.status(401).send("No token"); + try { + const decoded = jwt.verify(token, SECRET); + req.userId = decoded.userId; + next(); + } catch (err) { + res.status(401).send("Invalid token"); + } +} +``` + +### **Exercise (10 min)** + +- Add token expiration, test with Postman. +- Try to tamper with the token: what happens? + +**Discussion**: + +- When to use JWT vs. database tokens? +- How to handle token revocation? + +--- + +## **4. Session-Based Authentication (30 min)** + +**Goal**: Learn server-side sessions, compare with JWT. + +### **Lecture (5 min)** + +- **Concept**: Server stores session data, sends only ID to client. + +### **Implementation (15 min)** + +**Code Snippet**: + +```js +// app.js +const session = require("express-session"); +app.use( + session({ + secret: "your-secret", + resave: false, + saveUninitialized: false, + }), +); + +// routes/auth.js +router.post("/login", async (req, res) => { + const { username, password } = req.body; + const user = await db.getUserByUsername(username); + if (!user || user.password !== password) { + return res.status(401).send("Invalid credentials"); + } + req.session.userId = user.id; + res.send("Login successful"); +}); + +// middleware/auth.js +function isAuthenticated(req, res, next) { + if (req.session.userId) return next(); + res.status(401).send("Unauthorized"); +} +``` + +### **Exercise (10 min)** + +- Implement logout: destroy session. +- Compare with JWT: when would you use each? + +**Discussion**: + +- How does this scale? +- What about distributed systems? + +--- + +## **5. API Keys (15 min, if time)** + +**Goal**: Understand machine-to-machine auth. + +### **Lecture (5 min)** + +- **Concept**: Simple, permanent keys for scripts/services. + +### **Implementation (5 min)** + +**Code Snippet**: + +```js +// middleware/auth.js +function isAuthenticated(req, res, next) { + const apiKey = req.headers["x-api-key"]; + if (!apiKey || apiKey !== process.env.API_KEY) { + return res.status(401).send("Invalid API key"); + } + next(); +} +``` + +### **Exercise (5 min)** + +- Add rate limiting for API keys. +- Discuss: Why not use API keys for user auth? + +--- + +## **6. Wrap-up & Best Practices (15 min)** + +- **Recap Table**: + +Authentication Methods Comparison + +| Method | DB Lookup | Revocable | Scalable | Use Case | +| -------------------- | --------- | --------- | -------- | -------------------- | +| Database Credentials | Yes | No | No | Legacy systems | +| Database Tokens | Yes | Yes | No | Small apps | +| JWT | No | No\* | Yes | SPAs, mobile apps | +| Sessions | Yes | Yes | No\*\* | Traditional web apps | +| API Keys | No | No | Yes | Machine-to-machine | + +\*Unless using a blocklist +\*\*Unless using shared storage + +- **Best Practices**: + - Always use HTTPS. + - Hash passwords (bcrypt). + - Store tokens securely (HttpOnly cookies for web). + - Use short-lived tokens, refresh tokens if needed. + +- **Q&A**: What would you use for a mobile app? A microservice? A legacy system? From d5cad936f5e508c6d5e82bfdd81a0552773dce60 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 4 Mar 2026 16:49:55 +0100 Subject: [PATCH 2/3] feat: files outlines --- courses/backend/node/week3/README.md | 21 ++ courses/backend/node/week3/assignment.md | 159 +++++++++ courses/backend/node/week3/preparation.md | 13 + .../10-auth-db-credentials.md | 46 +++ .../session-materials/11-auth-db-tokens.md | 50 +++ .../week3/session-materials/12-auth-jwt.md | 50 +++ .../session-materials/13-auth-sessions.md | 50 +++ .../14-auth-api-keys-and-wrapup.md | 51 +++ courses/backend/node/week3/session-plan.md | 320 ++++++++---------- 9 files changed, 589 insertions(+), 171 deletions(-) create mode 100644 courses/backend/node/week3/session-materials/10-auth-db-credentials.md create mode 100644 courses/backend/node/week3/session-materials/11-auth-db-tokens.md create mode 100644 courses/backend/node/week3/session-materials/12-auth-jwt.md create mode 100644 courses/backend/node/week3/session-materials/13-auth-sessions.md create mode 100644 courses/backend/node/week3/session-materials/14-auth-api-keys-and-wrapup.md diff --git a/courses/backend/node/week3/README.md b/courses/backend/node/week3/README.md index e69de29b..c8db41d3 100644 --- a/courses/backend/node/week3/README.md +++ 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 index e69de29b..ec31e5be 100644 --- a/courses/backend/node/week3/assignment.md +++ 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 index e69de29b..efadda78 100644 --- a/courses/backend/node/week3/preparation.md +++ b/courses/backend/node/week3/preparation.md @@ -0,0 +1,13 @@ +# Preparation + +- Make sure you can run the **Snippets API** locally (pull the latest version from the course repository, install dependencies, and verify that the server starts without errors). +- Read a short introduction to **password hashing and salting** (for example, an article explaining why plaintext passwords are insecure and how bcrypt works) (10–15 min). +- Read a high-level overview of **JWT (JSON Web Tokens)** and how they are used for stateless authentication (10–15 min). +- Read a brief introduction to **cookies and sessions** in web applications (10–15 min). + +## Optional Resources + +For more research, you can explore the following resources: + +- OWASP cheatsheets on authentication and session management (for a deeper security perspective). +- A more in-depth article or video about JWT best practices (token lifetimes, refresh tokens, common pitfalls). 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-and-wrapup.md b/courses/backend/node/week3/session-materials/14-auth-api-keys-and-wrapup.md new file mode 100644 index 00000000..e82dd53e --- /dev/null +++ b/courses/backend/node/week3/session-materials/14-auth-api-keys-and-wrapup.md @@ -0,0 +1,51 @@ +# 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. + +## 4. 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). + +This comparison should prepare you for the reflection section of the assignment. + diff --git a/courses/backend/node/week3/session-plan.md b/courses/backend/node/week3/session-plan.md index 96abc549..3b30822d 100644 --- a/courses/backend/node/week3/session-plan.md +++ b/courses/backend/node/week3/session-plan.md @@ -1,273 +1,251 @@ -# **Session Plan: API Security & Authentication in Node.js** +# Session plan -**Duration**: 3.5 hours -**Format**: Lecture + Live Coding + Exercises +## 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) -## **1. Database-Stored Credentials (30 min)** +## Secure passwords & basic login (Snippets API) (≈40 min) -**Goal**: Understand basic auth flow, identify security flaws. +**Goal**: Implement secure password storage and a basic login flow for the Snippets API using bcrypt. -### **Lecture (10 min)** +### Lecture & live coding (≈10 min) -- **Concept**: Store username/password in DB, check on login. -- **Security Issues**: Plaintext passwords, no token management. +- 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 (15 min)** +### Implementation (live coding) -**Code Snippet**: +- Sketch the table and login route, for example: ```js // routes/auth.js const express = require("express"); +const bcrypt = require("bcrypt"); const router = express.Router(); -const db = require("../db"); +const db = require("../db"); // same Knex/db layer used by the Snippets API -// Login endpoint router.post("/login", async (req, res) => { const { username, password } = req.body; + const user = await db.getUserByUsername(username); - if (!user || user.password !== password) { - return res.status(401).send("Invalid credentials"); + 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.send("Login successful"); + + res.json({ message: "Login successful" }); }); module.exports = router; ``` -**Middleware**: - -```js -// middleware/auth.js -function isAuthenticated(req, res, next) { - if (req.session.user) return next(); - res.status(401).send("Unauthorized"); -} -``` +_See `./session-materials/10-auth-db-credentials.md` for a more detailed walkthrough._ -### **Exercise (5 min)** +### Exercise (15–20 min) -- Add a protected route `/profile` using `isAuthenticated`. -- Test with Postman: send username/password in body. +- 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. -**Discussion**: +### Do it together (5–10 min) -- Why is this insecure? -- What if the DB is compromised? +- Show one working solution. +- Discuss common mistakes (e.g. sending too much error detail, forgetting to hash passwords). --- -## **2. Database-Stored Tokens (45 min)** - -**Goal**: Improve security with tokens, understand token lifecycle. +## Stateless auth with JWT (≈40 min) -### **Lecture (10 min)** +**Goal**: Learn stateless auth with JWT on top of the secure login flow, and protect key Snippets API endpoints. -- **Concept**: Issue a random token on login, store in DB, validate on each request. -- **Pros/Cons**: Revocable, but DB lookup on every request. +### Lecture & live coding (≈10 min) -### **Implementation (20 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`. -**Code Snippet**: +### Implementation (live coding) ```js // routes/auth.js -const crypto = require("crypto"); +const jwt = require("jsonwebtoken"); +const SECRET = process.env.JWT_SECRET || "development-secret"; router.post("/login", async (req, res) => { const { username, password } = req.body; const user = await db.getUserByUsername(username); - if (!user || user.password !== password) { - return res.status(401).send("Invalid credentials"); + if (!user) { + return res.status(401).json({ error: "Invalid credentials" }); } - const token = crypto.randomBytes(32).toString("hex"); - await db.storeToken(token, user.id); - res.json({ token }); -}); - -// middleware/auth.js -async function isAuthenticated(req, res, next) { - const token = req.headers.authorization?.split(" ")[1]; - if (!token) return res.status(401).send("No token"); - const userId = await db.getUserIdByToken(token); - if (!userId) return res.status(401).send("Invalid token"); - req.userId = userId; - next(); -} -``` - -### **Exercise (10 min)** - -- Implement `/logout` endpoint: delete token from DB. -- Test with Postman: login, access `/profile`, logout, try again. - -**Discussion**: - -- How does this compare to the previous method? -- What if the DB is slow? - ---- - -## **3. JWT (JSON Web Tokens) (45 min)** - -**Goal**: Learn stateless auth, understand JWT structure and risks. - -### **Lecture (10 min)** - -- **Concept**: Self-contained tokens, no DB lookup. -- **Pros/Cons**: Fast, but hard to revoke. - -### **Implementation (20 min)** - -**Code Snippet**: - -```js -// routes/auth.js -const jwt = require("jsonwebtoken"); -const SECRET = "your-secret-key"; -router.post("/login", async (req, res) => { - const { username, password } = req.body; - const user = await db.getUserByUsername(username); - if (!user || user.password !== password) { - return res.status(401).send("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 }, SECRET, { expiresIn: "1h" }); res.json({ token }); }); -// middleware/auth.js -function isAuthenticated(req, res, next) { - const token = req.headers.authorization?.split(" ")[1]; - if (!token) return res.status(401).send("No token"); +// middleware/auth-jwt.js +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, SECRET); - req.userId = decoded.userId; + req.user = { id: decoded.userId }; next(); } catch (err) { - res.status(401).send("Invalid token"); + return res.status(401).json({ error: "Invalid or expired token" }); } } + +module.exports = { requireJwtAuth }; ``` -### **Exercise (10 min)** +_See `./session-materials/12-auth-jwt.md` for detailed steps and examples._ -- Add token expiration, test with Postman. -- Try to tamper with the token: what happens? +### Exercise (15–20 min) -**Discussion**: +- 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. -- When to use JWT vs. database tokens? -- How to handle token revocation? +### 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. -## **4. Session-Based Authentication (30 min)** +--- -**Goal**: Learn server-side sessions, compare with JWT. +## Session-based authentication (≈40 min) -### **Lecture (5 min)** +**Goal**: Implement session-based authentication using cookies and compare it to JWT for the Snippets API. -- **Concept**: Server stores session data, sends only ID to client. +### Lecture & live coding (≈10 min) -### **Implementation (15 min)** +- Concept: server-side sessions, session IDs in cookies, and typical use cases. +- Contrast with JWT: stateful vs stateless, revocation, and infrastructure needs. -**Code Snippet**: +### Implementation (live coding) ```js // app.js const session = require("express-session"); + app.use( session({ - secret: "your-secret", + secret: process.env.SESSION_SECRET || "development-session-secret", resave: false, saveUninitialized: false, }), ); -// routes/auth.js -router.post("/login", async (req, res) => { +// routes/auth-session.js +router.post("/login-session", async (req, res) => { const { username, password } = req.body; const user = await db.getUserByUsername(username); - if (!user || user.password !== password) { - return res.status(401).send("Invalid credentials"); + 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" }); + } + req.session.userId = user.id; - res.send("Login successful"); + res.json({ message: "Logged in with session" }); }); -// middleware/auth.js -function isAuthenticated(req, res, next) { - if (req.session.userId) return next(); - res.status(401).send("Unauthorized"); +function requireSessionAuth(req, res, next) { + if (req.session.userId) { + return next(); + } + res.status(401).json({ error: "Not authenticated" }); } -``` - -### **Exercise (10 min)** - -- Implement logout: destroy session. -- Compare with JWT: when would you use each? - -**Discussion**: -- How does this scale? -- What about distributed systems? - ---- +router.post("/logout-session", (req, res) => { + req.session.destroy(() => { + res.json({ message: "Logged out" }); + }); +}); +``` -## **5. API Keys (15 min, if time)** +_See `./session-materials/13-auth-sessions.md` for detailed guidance._ -**Goal**: Understand machine-to-machine auth. +### Exercise (15–20 min) -### **Lecture (5 min)** +- Protect at least one Snippets API route using session-based middleware. +- Implement logout and verify that access is denied after logging out. -- **Concept**: Simple, permanent keys for scripts/services. +### Do it together (5–10 min) -### **Implementation (5 min)** +- Compare the experience of working with sessions vs JWT. +- Discuss scaling concerns (sticky sessions, shared stores) at a high level. -**Code Snippet**: +--- -```js -// middleware/auth.js -function isAuthenticated(req, res, next) { - const apiKey = req.headers["x-api-key"]; - if (!apiKey || apiKey !== process.env.API_KEY) { - return res.status(401).send("Invalid API key"); - } - next(); -} -``` +## Comparison, DB tokens & API keys overview, and wrap-up (≈30–35 min) -### **Exercise (5 min)** +**Goal**: Compare the different auth approaches, introduce DB-stored tokens and API keys conceptually, and connect to the assignment. -- Add rate limiting for API keys. -- Discuss: Why not use API keys for user auth? +### 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: -## **6. Wrap-up & Best Practices (15 min)** +| 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 | -- **Recap Table**: +_See `./session-materials/11-auth-db-tokens.md` and `./session-materials/14-auth-api-keys-and-wrapup.md` for additional details and examples._ -Authentication Methods Comparison +### Optional mini-exercise (10–15 min, time permitting) -| Method | DB Lookup | Revocable | Scalable | Use Case | -| -------------------- | --------- | --------- | -------- | -------------------- | -| Database Credentials | Yes | No | No | Legacy systems | -| Database Tokens | Yes | Yes | No | Small apps | -| JWT | No | No\* | Yes | SPAs, mobile apps | -| Sessions | Yes | Yes | No\*\* | Traditional web apps | -| API Keys | No | No | Yes | Machine-to-machine | +- In pairs or small groups, sketch (or start implementing) either: + - A DB-token-based login flow, or + - An API-key-protected “machine” endpoint in the Snippets API. +- Make it clear that the full implementation can be completed as part of the assignment. -\*Unless using a blocklist -\*\*Unless using shared storage +### Final wrap-up (5–10 min) -- **Best Practices**: +- Reiterate best practices: - Always use HTTPS. - - Hash passwords (bcrypt). - - Store tokens securely (HttpOnly cookies for web). - - Use short-lived tokens, refresh tokens if needed. - -- **Q&A**: What would you use for a mobile app? A microservice? A legacy system? + - 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. +- Connect clearly to the assignment: + - Trainees will extend the snippets backend with DB tokens and/or API-key-protected endpoints. + - They will choose and justify which methods they would use in different scenarios. From 76a0e506da5aa7d5ffcc431cdec1062309f33ff8 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 4 Mar 2026 17:06:51 +0100 Subject: [PATCH 3/3] chore: altered links and examples --- .../module-materials/examples/auth-jwt.js | 64 +++++++ .../examples/auth-login-bcrypt.js | 40 +++++ .../examples/auth-sessions.js | 60 +++++++ courses/backend/node/week3/preparation.md | 14 +- ...keys-and-wrapup.md => 14-auth-api-keys.md} | 20 --- courses/backend/node/week3/session-plan.md | 162 ++++-------------- 6 files changed, 205 insertions(+), 155 deletions(-) create mode 100644 courses/backend/node/module-materials/examples/auth-jwt.js create mode 100644 courses/backend/node/module-materials/examples/auth-login-bcrypt.js create mode 100644 courses/backend/node/module-materials/examples/auth-sessions.js rename courses/backend/node/week3/session-materials/{14-auth-api-keys-and-wrapup.md => 14-auth-api-keys.md} (73%) 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/preparation.md b/courses/backend/node/week3/preparation.md index efadda78..757fbcbc 100644 --- a/courses/backend/node/week3/preparation.md +++ b/courses/backend/node/week3/preparation.md @@ -1,13 +1,15 @@ # Preparation -- Make sure you can run the **Snippets API** locally (pull the latest version from the course repository, install dependencies, and verify that the server starts without errors). -- Read a short introduction to **password hashing and salting** (for example, an article explaining why plaintext passwords are insecure and how bcrypt works) (10–15 min). -- Read a high-level overview of **JWT (JSON Web Tokens)** and how they are used for stateless authentication (10–15 min). -- Read a brief introduction to **cookies and sessions** in web applications (10–15 min). +//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). -- A more in-depth article or video about JWT best practices (token lifetimes, refresh tokens, common pitfalls). +- 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/14-auth-api-keys-and-wrapup.md b/courses/backend/node/week3/session-materials/14-auth-api-keys.md similarity index 73% rename from courses/backend/node/week3/session-materials/14-auth-api-keys-and-wrapup.md rename to courses/backend/node/week3/session-materials/14-auth-api-keys.md index e82dd53e..0280b4cb 100644 --- a/courses/backend/node/week3/session-materials/14-auth-api-keys-and-wrapup.md +++ b/courses/backend/node/week3/session-materials/14-auth-api-keys.md @@ -29,23 +29,3 @@ To prevent abuse, you can sketch (or implement) a very simple rate limiting stra - 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. - -## 4. 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). - -This comparison should prepare you for the reflection section of the assignment. - diff --git a/courses/backend/node/week3/session-plan.md b/courses/backend/node/week3/session-plan.md index 3b30822d..09c5dc15 100644 --- a/courses/backend/node/week3/session-plan.md +++ b/courses/backend/node/week3/session-plan.md @@ -7,7 +7,7 @@ - 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) (≈40 min) +## Secure passwords & basic login (Snippets API) **Goal**: Implement secure password storage and a basic login flow for the Snippets API using bcrypt. @@ -22,35 +22,10 @@ ### Implementation (live coding) -- Sketch the table and login route, for example: +- Sketch the table and login route, using the example in + [module-materials/examples/auth-login-bcrypt.js](../../module-materials/examples/auth-login-bcrypt.js). -```js -// routes/auth.js -const express = require("express"); -const bcrypt = require("bcrypt"); -const router = express.Router(); -const db = require("../db"); // same Knex/db layer used by the Snippets API - -router.post("/login", async (req, res) => { - const { username, password } = req.body; - - const user = await db.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" }); -}); - -module.exports = router; -``` - -_See `./session-materials/10-auth-db-credentials.md` for a more detailed walkthrough._ +_See [Secure passwords and basic login](./session-materials/10-auth-db-credentials.md) for a more detailed walkthrough._ ### Exercise (15–20 min) @@ -65,7 +40,7 @@ _See `./session-materials/10-auth-db-credentials.md` for a more detailed walkthr --- -## Stateless auth with JWT (≈40 min) +## Stateless auth with JWT **Goal**: Learn stateless auth with JWT on top of the secure login flow, and protect key Snippets API endpoints. @@ -80,48 +55,10 @@ _See `./session-materials/10-auth-db-credentials.md` for a more detailed walkthr ### Implementation (live coding) -```js -// routes/auth.js -const jwt = require("jsonwebtoken"); -const SECRET = process.env.JWT_SECRET || "development-secret"; - -router.post("/login", async (req, res) => { - const { username, password } = req.body; - const user = await db.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 }, SECRET, { expiresIn: "1h" }); - res.json({ token }); -}); - -// middleware/auth-jwt.js -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, SECRET); - req.user = { id: decoded.userId }; - next(); - } catch (err) { - return res.status(401).json({ error: "Invalid or expired token" }); - } -} - -module.exports = { requireJwtAuth }; -``` - -_See `./session-materials/12-auth-jwt.md` for detailed steps and examples._ +- 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) @@ -136,7 +73,7 @@ _See `./session-materials/12-auth-jwt.md` for detailed steps and examples._ --- -## Session-based authentication (≈40 min) +## Session-based authentication **Goal**: Implement session-based authentication using cookies and compare it to JWT for the Snippets API. @@ -147,50 +84,10 @@ _See `./session-materials/12-auth-jwt.md` for detailed steps and examples._ ### Implementation (live coding) -```js -// app.js -const session = require("express-session"); - -app.use( - session({ - secret: process.env.SESSION_SECRET || "development-session-secret", - resave: false, - saveUninitialized: false, - }), -); - -// routes/auth-session.js -router.post("/login-session", async (req, res) => { - const { username, password } = req.body; - const user = await db.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" }); - } - - 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" }); -} - -router.post("/logout-session", (req, res) => { - req.session.destroy(() => { - res.json({ message: "Logged out" }); - }); -}); -``` - -_See `./session-materials/13-auth-sessions.md` for detailed guidance._ +- 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) @@ -204,7 +101,7 @@ _See `./session-materials/13-auth-sessions.md` for detailed guidance._ --- -## Comparison, DB tokens & API keys overview, and wrap-up (≈30–35 min) +## 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. @@ -230,22 +127,29 @@ _See `./session-materials/13-auth-sessions.md` for detailed guidance._ | Sessions | Yes | Yes | Depends | Traditional web apps | | API keys | No | Not easy | Yes | Machine-to-machine | -_See `./session-materials/11-auth-db-tokens.md` and `./session-materials/14-auth-api-keys-and-wrapup.md` for additional details and examples._ +_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. -### Optional mini-exercise (10–15 min, time permitting) +Discuss trade-offs in terms of: -- In pairs or small groups, sketch (or start implementing) either: - - A DB-token-based login flow, or - - An API-key-protected “machine” endpoint in the Snippets API. -- Make it clear that the full implementation can be completed as part of the assignment. +- 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 (5–10 min) +### Final wrap-up - Reiterate best practices: - - Always use HTTPS. + - 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. -- Connect clearly to the assignment: - - Trainees will extend the snippets backend with DB tokens and/or API-key-protected endpoints. - - They will choose and justify which methods they would use in different scenarios.