Backend
Advanced 18 min read Backend

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.

OWASPsecurity headersrate limitingCORSinput validationTLSsecrets
Introduction

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.

Motivation

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.
Think about it

Mental model

Imagine

A fortified embassy

Think of

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.

Mechanics

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.

TLS termination
Rate limiter
CORS + headers
Input validation
Authentication
Object-level authorization
Handler returns shaped payload
Reference

Key commands

npm install helmet express-rate-limit

Install 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 protocol

Check which TLS versions a server accepts.

New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
npm audit

List 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)
Hands-on

Examples

Hardening an Express API with helmet, rate limit and validation

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 headers
app.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 profile
app.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.

Real world

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.
Guidelines

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.
Watch out

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.
Root cause analysis

Common issues

Error
403 instead of 401 for a valid token
Root cause

The token is valid but lacks permission, or object-level authorization failed.

Fix

Check the required scope/role and that the resource id belongs to the authenticated user.

Error
429 Too Many Requests during a load test
Root cause

The rate limiter bucket is too small or wrongly keyed (e.g. all requests share one IP bucket).

Fix

Key on user id where possible, raise the limit, and make the test send realistic headers.

Error
Cross-Origin Request Blocked in the browser console
Root cause

CORS does not allow the origin, or the request needs credentials that the wildcard origin forbids.

Fix

Add the exact origin to the allowlist and use Access-Control-Allow-Credentials: true without '*'.

When things break

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.
Interviews

Interview checkpoint

Practice

Hands-on lab

Harden an Express API end to end
45 min · advanced
In progress
Objectives
  • 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

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

A handler fetches a record by req.params.id and returns it with no ownership check. Which vulnerability is this?

2

Setting Access-Control-Allow-Origin: * is safe for endpoints that use credentialed requests (cookies).

3

Which practice best prevents excess data exposure in API responses?

Quick reference

Cheat sheet

Headers & middleware
helmet()Security headers in one middleware
Strict-Transport-SecurityForce HTTPS for a period
X-Content-Type-Options: nosniffBlock MIME sniffing
Content-Security-PolicyRestrict what the page may load
Checks
npm auditFind vulnerable dependencies
Rate limit loginBlock brute force (e.g. 20/15m)
Object owner checkCompare resource owner vs user
openssl s_clientVerify TLS in production
Go deeper

Official documentation