API Security
Public APIs are prime attack targets. This concept applies the OWASP API Security Top 10 — broken object authorization, excess data exposure, injection, rate limiting, and misconfiguration — plus HTTPS, CORS, secrets management and the security headers that harden a Node backend.
What is this?
API security is the discipline of finding and closing the ways an API can be abused: broken object-level authorization, injection, excess data exposure, missing rate limiting, unsafe CORS, and misconfiguration. Defense-in-depth layers TLS everywhere, validates every input, authenticates and authorizes every request, restricts what browsers may call, limits abuse with rate limiting, and never trusts anything a client sends.
Why do you need it?
- Broken object-level authorization is the #1 API risk: most breaches come from guessing an id and getting another user's data.
- Rate limiting and request validation stop abuse like credential stuffing, scraping and payload floods.
- TLS and security headers (HSTS, CSP, X-Content-Type-Options) close browser- and transport-level attacks.
- The OWASP API Security Top 10 is a shared, actionable checklist that audits and pentests are measured against.
Mental model
A fortified embassy
A secured API
Every door needs a badge scan (auth), visitors only reach areas their clearance allows (authorization), the mail room inspects all parcels (input validation), and there is a limit on how many visitors arrive per hour (rate limiting). A single unlocked broom closet (a misconfigured CORS rule or a leaked secret) can compromise the whole building.
How it works
- Every endpoint enforces authentication and object-level checks: after fetching a resource by id, verify the current user owns or may access it.
- All inputs are validated against a schema (types, length, allowed values) before any query or operation uses them.
- Responses are shaped explicitly — never dump whole database rows — to prevent excess data exposure.
- Rate limiting and payload size limits sit in front of handlers, keyed by user/IP, returning 429 on abuse.
- Transport uses TLS, security headers are set, CORS allows only known origins, and secrets live in environment variables or a vault.
The layered defense of a hardened API request path.
Key commands
npm install helmet express-rate-limitInstall security headers and rate limiting middleware.
added 15 packages in 1s
curl -sI https://api.example.com/ | grep -i -E 'strict-transport|content-security|x-content-type'Verify security headers are present on a production response.
strict-transport-security: max-age=63072000; includeSubDomains
openssl s_client -connect api.example.com:443 -servername api.example.com < /dev/null | grep -i protocolCheck which TLS versions a server accepts.
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
npm auditList known vulnerabilities in your dependency tree.
found 0 vulnerabilities
node -e "require('crypto').randomBytes(48).toString('base64')"Generate a strong random secret for signing or encryption.
Kf3hT9x... (random base64)
Examples
Hardening an Express API with helmet, rate limit and validation
const express = require("express")const helmet = require("helmet")const { rateLimit } = require("express-rate-limit") const app = express()app.use(helmet()) // security headersapp.use(express.json({ limit: "100kb" })) const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 20, standardHeaders: true, legacyHeaders: false, message: { error: "too many attempts" },})app.use("/auth/login", authLimiter) // Object-level authorization: users can only touch their own profileapp.get("/users/:id", (req, res) => { if (req.params.id !== req.user.sub) { return res.status(403).json({ error: "forbidden" }) } return res.json({ id: req.user.sub, email: req.user.email }) // shaped, not the full row}) app.listen(3000)helmet() sets headers like X-Content-Type-Options and HSTS. The login route gets a strict rate limit (20 attempts per 15 minutes). The profile route checks ownership before returning data and responds only with projected fields — never a whole database row — which stops both broken object authorization and excess data exposure.
In production
- Fintech and health APIs enforce object-level authorization, audit logging and PII field shaping on every endpoint.
- API gateways (Kong, nginx, AWS WAF) run TLS, rate limits and bot detection in front of microservices.
- Public APIs use scoped OAuth tokens, per-key rate limits and usage quotas for billing and abuse control.
- CI pipelines run npm audit, secret scanning and dependency upgrades so vulnerabilities are caught before deploy.
Best practices
- Enforce object-level authorization in every handler — never trust that the caller 'should not have sent that id'.
- Validate all input against a schema and reject unknown fields; serialize only the fields clients need.
- Terminate TLS at the edge, set HSTS, and refuse TLS versions below 1.2.
- Apply rate limiting with per-user and per-IP buckets, and return Retry-After on 429.
- Keep secrets in environment variables or a vault; rotate them and scan the repo for accidental commits.
Common mistakes
- Checking auth only, then fetching a resource by a client-supplied id with no ownership check (IDOR).
- Returning the full database row (password hashes, internal flags) in JSON responses.
- Setting CORS to Access-Control-Allow-Origin: * for endpoints that carry cookies or credentials.
- Logging tokens, passwords or PII into application logs.
Common issues
403 instead of 401 for a valid tokenThe token is valid but lacks permission, or object-level authorization failed.
Check the required scope/role and that the resource id belongs to the authenticated user.
429 Too Many Requests during a load testThe rate limiter bucket is too small or wrongly keyed (e.g. all requests share one IP bucket).
Key on user id where possible, raise the limit, and make the test send realistic headers.
Cross-Origin Request Blocked in the browser consoleCORS does not allow the origin, or the request needs credentials that the wildcard origin forbids.
Add the exact origin to the allowlist and use Access-Control-Allow-Credentials: true without '*'.
Debugging tips
- curl -k is for local testing only; in production always verify the TLS handshake succeeds without flags.
- Use Security Headers analyzer or curl -sI to diff headers before/after adding helmet.
- Check your rate limiter's keying: X-Forwarded-For must be trusted (proxy sets it), otherwise everyone shares one bucket.
- Grep logs for non-2xx spikes around a release to catch misconfigurations early.
Interview checkpoint
Hands-on lab
- Add security headers with helmet
- Rate limit a login endpoint
- Fix an object-level authorization (IDOR) flaw
- Shape responses to prevent excess data exposure
Harden an Express API end to end
45 min · advanced
Quick quiz
A handler fetches a record by req.params.id and returns it with no ownership check. Which vulnerability is this?
Setting Access-Control-Allow-Origin: * is safe for endpoints that use credentialed requests (cookies).
Which practice best prevents excess data exposure in API responses?
Cheat sheet
helmet()Security headers in one middlewareStrict-Transport-SecurityForce HTTPS for a periodX-Content-Type-Options: nosniffBlock MIME sniffingContent-Security-PolicyRestrict what the page may loadnpm auditFind vulnerable dependenciesRate limit loginBlock brute force (e.g. 20/15m)Object owner checkCompare resource owner vs useropenssl s_clientVerify TLS in production