REST APIs & JSON
Design and consume RESTful APIs: resource naming, HTTP methods, status codes, authentication patterns, and API security basics.
Learning Objectives
- → Explain REST architectural constraints
- → Design RESTful URL schemas and select correct HTTP methods
- → Implement API authentication with API keys and JWT
- → Understand CORS and how to configure it safely
- → Identify common API security vulnerabilities
REST Constraints
REST (Representational State Transfer) is an architectural style with 6 constraints:
- Client-Server — UI and data separated
- Stateless — each request self-contained (no server session)
- Cacheable — responses must define cacheability
- Uniform Interface — consistent resource URIs + HTTP methods
- Layered System — client can't tell if it's talking to origin or proxy
- Code on Demand (optional) — server can send executable code
Resource Design
# Good REST URL design
GET /api/v1/scans → list all scans
POST /api/v1/scans → create new scan
GET /api/v1/scans/{id} → get scan by ID
PATCH /api/v1/scans/{id} → update scan
DELETE /api/v1/scans/{id} → delete scan
GET /api/v1/hosts/{ip}/ports → ports for a host
POST /api/v1/hosts/{ip}/scan → trigger scan on host
# Bad URL design (actions in URL)
POST /api/createScan ✗
GET /api/getScan?id=123 ✗
POST /api/deleteScan ✗
HTTP Status Codes for APIs
| Status | Meaning | When to use |
|---|---|---|
| 200 | OK | Successful GET, PATCH |
| 201 | Created | Successful POST (new resource) |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input |
| 401 | Unauthorized | Missing/invalid auth |
| 403 | Forbidden | Auth OK but no permission |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate resource |
| 422 | Unprocessable Entity | Validation errors |
| 429 | Too Many Requests | Rate limited |
| 500 | Internal Server Error | Unexpected server error |
API Authentication
API Key
GET /api/scans HTTP/1.1
Authorization: Bearer sk-abc123...
X-API-Key: sk-abc123... (alternative)
JWT (JSON Web Token)
JWT structure: header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjN9.HMAC_SHA256
// Express JWT middleware
const jwt = require("jsonwebtoken");
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "No token" });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (err) {
res.status(401).json({ error: "Invalid token" });
}
}
// Issue token on login
app.post("/auth/login", async (req, res) => {
const { username, password } = req.body;
const user = await validateUser(username, password);
if (!user) return res.status(401).json({ error: "Invalid credentials" });
const token = jwt.sign(
{ user_id: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.json({ token });
});
CORS Configuration
const cors = require("cors");
// Allow specific origins only
app.use(cors({
origin: ["https://app.example.com", "https://staging.example.com"],
methods: ["GET", "POST", "PATCH", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
}));
// NEVER do this in production:
app.use(cors({ origin: "*", credentials: true })); // INSECURE
Rate Limiting
const rateLimit = require("express-rate-limit");
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: { error: "Too many requests, try again later" },
standardHeaders: true, // Return RateLimit-* headers
});
app.use("/api", limiter);
Common API Vulnerabilities (OWASP API Top 10)
| # | Vulnerability | Example |
|---|---|---|
| 1 | Broken Object Level Auth | GET /api/orders/123 — can access other users' orders |
| 2 | Broken Auth | Weak JWT secret, no expiry |
| 3 | Broken Object Property Auth | Mass assignment: user sets role=admin via body |
| 4 | Unrestricted Resource Use | No rate limiting on expensive endpoints |
| 5 | Broken Function Level Auth | /admin routes accessible without admin role |
| 6 | Unrestricted Access to Sensitive Flows | No CAPTCHA on reset-password endpoint |
| 7 | SSRF | fetch(req.body.url) fetches internal services |
| 8 | Security Misconfiguration | CORS *, verbose error messages in prod |
| 9 | Improper Inventory Management | Old v1 API still accessible and unpatched |
| 10 | Unsafe Consumption of APIs | Trusting 3rd party API data without validation |
Mini Project – Versioned Scan API
const express = require("express");
const rateLimit = require("express-rate-limit");
const app = express();
app.use(express.json());
const scans = new Map();
const limiter = rateLimit({ windowMs: 60000, max: 30 });
function auth(req, res, next) {
if (req.headers["x-api-key"] !== process.env.API_KEY) {
return res.status(401).json({ error: "Unauthorized" });
}
next();
}
const v1 = express.Router();
v1.use(limiter);
v1.use(auth);
v1.get("/scans", (req, res) => {
res.json({ scans: [...scans.values()] });
});
v1.post("/scans", (req, res) => {
const { host } = req.body;
if (!host) return res.status(400).json({ error: "host required" });
const id = Date.now().toString();
scans.set(id, { id, host, status: "queued" });
res.status(201).json(scans.get(id));
});
v1.get("/scans/:id", (req, res) => {
const scan = scans.get(req.params.id);
if (!scan) return res.status(404).json({ error: "Not found" });
res.json(scan);
});
app.use("/api/v1", v1);
app.listen(3000, () => console.log("API on :3000"));
Design REST endpoints for a vulnerability tracker: create, list, get, update, delete vulnerabilities. Also design endpoints for: mark vuln as fixed, list vulns by severity, get vuln history. Write them in a table: METHOD | PATH | Description.
What HTTP method updates a partial resource (not the whole thing)?
What status code should a successful POST (resource created) return?
Extend the Express scan API with JWT auth: POST /auth/login returns a JWT token (use jsonwebtoken package). Protect all /api/v1 routes with the auth middleware. Test: get a token, use it in Authorization: Bearer header, try without token.
What are the three parts of a JWT separated by dots?
Where should JWT secrets be stored in Node.js?
Add express-rate-limit with 10 requests/minute on the scan endpoints. Add CORS allowing only http://localhost:3000. Test rate limit by sending 11 requests quickly with a loop. Verify 429 on the 11th.
What HTTP status code means 'Too Many Requests'?