// Security
Securing Node.js APIs: An OWASP-Driven Checklist
A practical, OWASP-driven security checklist for Node.js APIs: access control, injection, validation, dependencies, SSRF and secrets.
Md Nayeem HossainApr 28, 20253 min read// Security
A practical, OWASP-driven security checklist for Node.js APIs: access control, injection, validation, dependencies, SSRF and secrets.
Md Nayeem HossainApr 28, 20253 min readSecurity reviews tend to happen right before launch, when there is no time to fix what they find. I prefer a checklist the whole team applies continuously, during design and code review. This one is organized around the OWASP Top 10, with the specific Node.js practices I enforce.
This is the most common and most damaging category. Authentication answers "who are you?"; authorization answers "are you allowed to do this to this resource?" Most bugs live in the second question.
1// Vulnerable: any logged-in user can read any invoice by ID2@Get(":id")3getInvoice(@Param("id") id: string) {4 return this.invoices.findById(id);5}6 7// Safe: ownership is part of the query8@Get(":id")9getInvoice(@Param("id") id: string, @CurrentUser() user: User) {10 return this.invoices.findOneOrFail({ id, accountId: user.accountId });11}Rules:
Never build queries by concatenating strings. Use parameterized queries or an ORM's query builder:
1// Vulnerable2db.query(`SELECT * FROM users WHERE email = '${email}'`);3 4// Safe5db.query("SELECT * FROM users WHERE email = $1", [email]);The same applies to NoSQL. With MongoDB, an attacker can send { "$gt": "" } as a password field. Validate input types strictly so objects never reach a query where a string was expected.
Validate every request body, query string and header against a schema, and reject unknown fields:
1const CreatePayoutSchema = z.object({2 affiliateId: z.string().uuid(),3 amountCents: z.number().int().positive().max(10_000_000),4}).strict();strict() blocks mass assignment, where an attacker adds "role": "admin" to a profile update request.
Most production incidents in this category are defaults nobody changed:
X-Content-Type-Options, a content security policy.origin: "*" combined with credentials is a bug.X-Powered-By header, debug endpoints and default credentials.The average Node.js project has hundreds of transitive dependencies. Each is code you run with full privileges.
npm audit (or a dependency scanner) in CI and fail on high-severity issues.npm ci in builds, so you deploy exactly what you tested.Any feature that fetches a user-provided URL — webhooks, link previews, image imports — can be abused to reach internal services or cloud metadata endpoints.
You cannot respond to an attack you cannot see. Log authentication failures, permission denials and unusual activity — with user IDs and request IDs, but never passwords or tokens. Alert on spikes, such as hundreds of failed logins from one IP.
The checklist only works if it is used routinely:
Most API breaches are not sophisticated. They come from a missing ownership check, an unvalidated field, an outdated dependency or an overly permissive default. A short checklist, applied to every change, closes the vast majority of those doors before attackers find them.