Module: Nodejs
Nodejs·127·6 MIN READ

127: Node/Express API Security — Validation, CORS, Cookies, Sessions, Headers, Rate Limits, and Abuse Resistance

TOPICS COVERED: Node/Express API Security — Validation, CORS, Cookies, Sessions, Headers, Rate Limits, and Abuse Resistance

Learning objectives

You will learn to:

  • treat every network input as untrusted;
  • validate and normalize inputs;
  • understand CORS correctly;
  • configure security headers;
  • understand cookies and session security;
  • distinguish authentication from authorization;
  • understand rate limiting and abuse controls;
  • defend against body-size, regex, query, and resource exhaustion;
  • understand proxy/IP trust;
  • prevent common injection and SSRF classes;
  • manage secrets safely;
  • create a practical Node API security checklist.

Security is layered

A secure API needs multiple controls:

text
network/TLS
↓
reverse proxy
↓
request limits
↓
authentication
↓
authorization
↓
validation
↓
business invariants
↓
database safe queries
↓
safe response
↓
logging/audit

No single middleware “secures Express.”

Input sources

Untrusted:

text
req.params
req.query
req.body
req.headers
cookies
uploaded files
JWT claims unless cryptographically verified
webhook payloads
upstream API responses
database data originally supplied by users

Validate at trust boundaries.

Schema validation

Example with a schema library:

js
const CreateTaskSchema = z.object({
  title: z
    .string()
    .trim()
    .min(3)
    .max(80),

  priority: z
    .enum(['low', 'normal', 'high'])
    .default('normal'),
}).strict();

Strict/reject unknown fields where appropriate.

Then:

js
const result = CreateTaskSchema.safeParse(req.body);

if (!result.success) {
  throw new ValidationError(...);
}

const input = result.data;

Do not pass raw body deeper after validation.

Use parsed value.

Type coercion

Query values arrive as strings/structures.

Be explicit:

text
"false" ≠ false
"0" ≠ 0

Schema coercion can help, but test empty string, repeated params, arrays, and malicious objects.

Prototype pollution

Some object-merging/parsing packages historically suffered prototype-pollution issues.

Keep dependencies patched.

Do not deep-merge arbitrary request objects into configuration/domain objects.

Prefer explicit field selection.

NoSQL/operator injection preview

Dangerous pattern later with Mongo:

js
collection.find(req.query);

Attacker may supply operator-shaped query values.

Never use request object directly as database filter.

Build filter from validated allowed primitives.

SQL injection principle

Even if this module later uses Mongo, know the general rule:

Use parameterized queries/ORM builders.

Never concatenate untrusted strings into SQL.

Command injection

Danger:

js
exec(`convert ${req.body.filename} output.png`);

Shell metacharacters can execute commands.

Prefer execFile/spawn with argument arrays:

js
spawn('convert', [
  validatedInputPath,
  outputPath,
]);

Still validate paths/options.

Avoid invoking shell unless required.

Path traversal

Covered in filesystem lesson.

Never map request path directly to filesystem.

SSRF

Covered in HTTP lesson.

User-controlled outbound URL can access internal services/cloud metadata.

Use destination policy + egress controls.

CORS

CORS controls whether browser JavaScript can read/call cross-origin resources under browser policy.

It does not authenticate requests.

A curl script/server attacker ignores browser CORS.

Example:

js
import cors from 'cors';

app.use(
  cors({
    origin(origin, callback) {
      if (!origin) {
        return callback(null, true);
      }

      if (allowedOrigins.has(origin)) {
        return callback(null, true);
      }

      callback(new Error('Origin not allowed'));
    },
    credentials: true,
  }),
);

Do not reflect arbitrary Origin when credentials are enabled.

Preflight

Browser may send:

text
OPTIONS
Access-Control-Request-Method
Access-Control-Request-Headers

Framework CORS middleware can handle.

Do not confuse preflight success with authorization.

Security headers

Helmet is a common Express package:

bash
npm install helmet
js
import helmet from 'helmet';

app.use(helmet());

It helps set security-related HTTP headers.

Still review policies:

  • CSP;
  • HSTS;
  • frame protections;
  • referrer policy;
  • MIME sniffing.

Do not disable protections globally to fix one frontend issue.

CSP

Content Security Policy is especially relevant if Node serves browser pages.

JSON-only APIs still benefit from other headers but CSP primarily controls browser content execution.

If React is separately hosted, configure CSP at frontend delivery layer.

Cookies

Session cookie:

text
HttpOnly
Secure
SameSite
Path
Domain carefully
Max-Age/Expires

HttpOnly reduces JavaScript access.

Secure requires HTTPS.

SameSite reduces some cross-site request risks.

Cookie name/value alone is not security.

Session IDs

A session cookie should contain an unpredictable session identifier, not raw sensitive session data unless using a carefully designed signed/encrypted format.

Server-side session store maps:

text
sessionId → user/session data

Session fixation prevention and rotation matter at login/privilege changes.

CSRF

Cookie-based authentication can be sent automatically by browser on requests.

Cross-Site Request Forgery can trick browser into submitting authenticated action.

Mitigations depend on architecture:

  • SameSite cookies;
  • CSRF tokens;
  • Origin/Referer validation;
  • custom headers + CORS;
  • framework strategy.

Do not assume JSON automatically eliminates CSRF.

Bearer tokens

Authorization header:

text
Authorization: Bearer <token>

Bearer means whoever possesses token can use it.

Protect:

  • transmission via TLS;
  • storage;
  • logs;
  • rotation/revocation strategy.

Do not put access tokens in URLs where logs/history may leak them.

JWT misconception

JWT is a token format, not an authentication system by itself.

You still need:

  • issuer;
  • audience;
  • signature verification;
  • algorithm policy;
  • expiration;
  • key rotation;
  • revocation/session policy;
  • authorization checks.

Authentication lesson goes deeper.

Passwords

Never store plaintext passwords.

Use modern password-hashing functions designed for passwords, such as Argon2id/bcrypt/scrypt according to current security guidance and ecosystem support.

Node's crypto supports scrypt.

Third-party password packages must be maintained.

Use salts as designed by algorithm/library.

Do not invent hashing scheme:

text
SHA256(password)

alone.

Rate limiting

Rate limits control abuse/resource use.

Example categories:

text
login attempts
password reset
expensive search
public API
upload

A simple IP-only limit can hurt users behind NAT and is bypassable with distributed attackers.

Combine dimensions when appropriate:

  • account;
  • API key;
  • IP;
  • tenant;
  • endpoint cost.

In multi-instance deployment, in-memory rate limiter must use shared store or edge control for consistent global limits.

429

Return:

text
429 Too Many Requests

and possibly Retry-After.

Do not reveal excessive security detail.

Request size

Configure:

js
express.json({
  limit: '256kb',
});

Upload endpoints get separate limits.

Also consider reverse proxy limits.

Defense in depth.

Timeout

Slow clients/upstreams can consume resources.

Configure:

  • server header/request timeouts;
  • proxy timeouts;
  • upstream fetch deadlines;
  • database query limits.

Do not set arbitrary 2-second timeout for every operation. Understand expected workloads.

Regex DoS

Unsafe regex can have catastrophic backtracking on attacker-controlled input.

Prefer safe patterns, bounded lengths, and modern engines/features.

Do not run complex regex over megabytes of input.

JSON/body CPU

Even before business logic, parsing huge JSON costs memory/CPU.

Body limits are security and performance controls.

Pagination abuse

Reject/clamp:

text
limit=10000000

Enforce max filters/date ranges.

A query can be valid but operationally expensive.

Database timeouts

Use database driver timeout/maxTimeMS where appropriate.

Do not allow public endpoint to trigger unbounded full collection scan.

Indexes/data modeling are security availability concerns too.

Trust proxy and IP

Express:

js
app.set('trust proxy', ...)

If misconfigured, attacker can forge forwarded IP and bypass:

  • secure cookie detection;
  • rate limits;
  • IP access rules.

Model actual proxy hops.

Logging security

Never log:

text
password
Authorization bearer token
session cookie
credit-card full number
private key
full sensitive form body

Use redaction.

Logs are data stores.

Protect retention/access.

Error security

Unexpected:

json
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "The request could not be completed."
  }
}

Log internal details privately.

Do not return Mongo connection strings, stack frames, filesystem paths.

Dependency security

  • supported Node LTS;
  • supported Express;
  • lockfile;
  • npm audit/advisories;
  • dependency review;
  • minimal packages;
  • automated updates with tests.

Patch runtime too, not only npm packages.

TLS

Use HTTPS.

If TLS terminates at proxy, secure the proxy-to-app network according to threat model.

Do not accept plaintext secrets over public network.

Authorization is server-side

Client hides Delete button:

jsx
{canDelete && <button>Delete</button>}

good UX.

Server must still check:

text
authenticated user
tenant
role/permissions
resource ownership
resource status

for every operation.

Multi-tenant query

Never trust body:

json
{ "tenantId": "victim" }

to scope data.

Derive tenant from authenticated server context.

Query:

js
repository.findOne({
  tenantId: auth.tenantId,
  id: taskId,
});

Prevent IDOR/BOLA.

API security checklist

Before release:

text
[ ] supported Node/Express
[ ] TLS
[ ] body/upload limits
[ ] schema validation
[ ] unknown fields policy
[ ] authentication
[ ] per-resource authorization
[ ] tenant scoping
[ ] CORS policy
[ ] CSRF policy
[ ] secure cookies/token storage
[ ] security headers
[ ] rate limits
[ ] upstream timeouts
[ ] database timeouts
[ ] query cost limits
[ ] path/SSRF/command injection review
[ ] secret redaction
[ ] safe errors
[ ] dependency/runtime patching
[ ] audit logs for sensitive actions

Exercises

  1. Add strict schema validation to Task API.
  2. Demonstrate CORS does not block curl.
  3. Configure Helmet and inspect headers.
  4. Design cookie session attributes.
  5. Threat-model CSRF for cookie auth.
  6. Add shared-store rate-limit architecture design.
  7. Build safe outbound URL allowlist.
  8. Fix command-injection example with spawn args.
  9. Design tenant-scoped repository query.
  10. Write abuse test cases for body/page limits.

Mastery checklist

Explain:

  • validation;
  • CORS;
  • CSP/headers;
  • sessions/cookies;
  • CSRF;
  • bearer/JWT limitations;
  • password hashing;
  • rate limits;
  • body/query limits;
  • trust proxy;
  • SSRF/path/command injection;
  • authorization/tenant scoping;
  • safe logging/errors.

Official references