Skip to content

Security — Practical

import argon2 from 'argon2';
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, timeCost: 3, parallelism: 1,
});
const ok = await argon2.verify(hash, candidate);
import { jwtVerify, createRemoteJWKSet } from 'jose';
const JWKS = createRemoteJWKSet(new URL(`${process.env.IDP}/.well-known/jwks.json`));
export async function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).end();
try {
const { payload } = await jwtVerify(token, JWKS, {
issuer: process.env.IDP,
audience: 'api.example.com',
algorithms: ['RS256','ES256'],
});
req.user = payload;
next();
} catch {
res.status(401).end();
}
}
const requireRole = (role: string) => (req, res, next) =>
req.user?.roles?.includes(role) ? next() : res.status(403).end();
app.delete('/admin/users/:id', authenticate, requireRole('admin'), handler);

Authorization at the data layer (PG row-level security)

Section titled “Authorization at the data layer (PG row-level security)”
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
CREATE POLICY owner_only ON documents
USING (owner_id = current_setting('app.user_id')::uuid);
-- on every connection
SET app.user_id = '...';
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
app.use(rateLimit({
store: new RedisStore({ sendCommand: (...a) => redis.call(...a) }),
windowMs: 60_000, max: 100,
standardHeaders: 'draft-7',
keyGenerator: (req) => req.user?.id ?? req.ip,
}));
async function recordFailedLogin(email: string) {
const k = `fail:${email}`;
const n = await redis.incr(k);
if (n === 1) await redis.expire(k, 900);
if (n >= 5) throw new Error('locked: try again in 15m');
}
import { z } from 'zod';
const Body = z.object({
email: z.string().email(),
age: z.number().int().min(13).max(120),
role: z.enum(['user','admin']).default('user'),
});
app.post('/users', (req, res) => {
const data = Body.parse(req.body); // throws 400 via error handler
...
});
import { URL } from 'url';
import dns from 'dns/promises';
const PRIVATE = [
/^10\./, /^192\.168\./, /^172\.(1[6-9]|2\d|3[01])\./, /^127\./, /^169\.254\./, /^::1$/, /^fc00:/, /^fe80:/,
];
async function safeFetch(input: string) {
const u = new URL(input);
if (!['http:', 'https:'].includes(u.protocol)) throw new Error('proto');
const { address } = await dns.lookup(u.hostname);
if (PRIVATE.some(re => re.test(address))) throw new Error('private');
return fetch(input, { redirect: 'manual' });
}
res.cookie('sid', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 1000 * 60 * 60 * 4, // 4h
});
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
},
},
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
}));

CSRF protection (csurf alternative for non-API forms)

Section titled “CSRF protection (csurf alternative for non-API forms)”

For SPA + cookies: double-submit cookie + custom header check on state-changing requests.

app.use((req, res, next) => {
if (req.method === 'GET') {
res.cookie('csrf', crypto.randomUUID(), { sameSite: 'lax' });
} else {
if (req.cookies.csrf !== req.headers['x-csrf']) return res.status(403).end();
}
next();
});
import VaultClient from 'node-vault';
const vault = VaultClient({ endpoint: 'https://vault:8200', token: process.env.VAULT_TOKEN });
const { data } = await vault.read('secret/data/db');
const dbPassword = data.data.password;

In Kubernetes, use External Secrets Operator to sync into K8s Secrets, mount as env or files.

import https from 'https';
import fs from 'fs';
https.createServer({
cert: fs.readFileSync('cert.pem'),
key: fs.readFileSync('key.pem'),
minVersion: 'TLSv1.2',
}, app).listen(8443);

For prod: terminate at LB / ingress; backend speaks plaintext over private network or mTLS via mesh.

Record sensitive actions (auth events, role changes, data exports, privileged ops):

await audit.log({
actor: req.user.id, action: 'role.change', resource: target.id,
meta: { from: prev, to: next }, ip: req.ip, ua: req.headers['user-agent'],
});

Keep audit log immutable / append-only; ship to separate store (S3 with Object Lock, immutable bucket).

  • npm audit / pip-audit / govulncheck in CI.
  • Snyk / Dependabot for upgrades.
  • Pin versions; lockfiles committed.
  • SBOM generation (Syft) for compliance.
  • All inputs validated (zod / pydantic / joi).
  • All DB calls parameterized.
  • No secrets in code / env files committed.
  • Auth + authz on every endpoint.
  • Rate limiting on auth endpoints.
  • HTTPS + HSTS.
  • Cookies HttpOnly + Secure + SameSite.
  • Logs scrub PII / tokens.
  • Dependencies scanned, no critical CVEs.
  • Container runs non-root, read-only fs where possible.