Loading introduction
Loading stats
Loading about
Loading posts
Loading article
Skip to content
Md Nayeem Hossain
Explorer
home.tsx
securing-nodejs-apis-owasp-checklist.md
securing-nodejs-apis-owasp-checklist.md
blog.json

// 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
🔐

Securing Node.js APIs: An OWASP-Driven Checklist#

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

1. Broken access control#

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.

ts
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:

  • Deny by default; require explicit permission checks on every route.
  • Check ownership in the data query itself, not in a separate step that can be forgotten.
  • Use non-sequential IDs (UUIDs), but never rely on them for security.

2. Injection#

Never build queries by concatenating strings. Use parameterized queries or an ORM's query builder:

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

3. Validate everything at the boundary#

Validate every request body, query string and header against a schema, and reject unknown fields:

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

4. Authentication failures#

  • Hash passwords with bcrypt or argon2, never a fast hash like SHA-256.
  • Rate-limit login, password reset and OTP endpoints aggressively.
  • Return the same error for "unknown email" and "wrong password", and spend the same time on both, so attackers cannot enumerate accounts.
  • Use short-lived access tokens and rotate refresh tokens.

5. Security misconfiguration#

Most production incidents in this category are defaults nobody changed:

  • Set security headers with Helmet: HSTS, X-Content-Type-Options, a content security policy.
  • Configure CORS with an explicit allowlist of origins. origin: "*" combined with credentials is a bug.
  • Disable stack traces and detailed errors in production responses.
  • Remove the X-Powered-By header, debug endpoints and default credentials.

6. Vulnerable dependencies#

The average Node.js project has hundreds of transitive dependencies. Each is code you run with full privileges.

  • Run npm audit (or a dependency scanner) in CI and fail on high-severity issues.
  • Commit your lockfile and use npm ci in builds, so you deploy exactly what you tested.
  • Review new dependencies before adding them: maintenance activity, download counts, install scripts.
  • Keep Node.js itself on a supported LTS version.

7. Server-side request forgery (SSRF)#

Any feature that fetches a user-provided URL — webhooks, link previews, image imports — can be abused to reach internal services or cloud metadata endpoints.

  • Allowlist destinations where possible.
  • Resolve the hostname and block private, loopback and link-local IP ranges.
  • Disable redirects, or re-validate after each one.

8. Secrets management#

  • Never commit secrets. Add a secret scanner to your pre-commit hooks and CI.
  • Load secrets from environment variables or a secret manager, and validate them at startup.
  • Rotate secrets that may have been exposed, including ones pasted into chat or tickets.
  • Give each service its own credentials with the minimum permissions it needs.

9. Logging and monitoring#

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.

10. Make security part of the workflow#

The checklist only works if it is used routinely:

  • Add security questions to the pull request template.
  • Threat-model new features that handle money, authentication or personal data.
  • Write tests for authorization: user A must not be able to access user B's resources.

Conclusion#

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.

#Security#Node.js#OWASP#API
OlderThe Transactional Outbox Pattern: Reliable Events Without Distributed TransactionsNewer Caching Strategies That Don't Lie: Invalidation in Practice

Outline

  • Securing Node.js APIs: An OWASP-Driven Checklist
  • 1. Broken access control
  • 2. Injection
  • 3. Validate everything at the boundary
  • 4. Authentication failures
  • 5. Security misconfiguration
  • 6. Vulnerable dependencies
  • 7. Server-side request forgery (SSRF)
  • 8. Secrets management
  • 9. Logging and monitoring
  • 10. Make security part of the workflow
  • Conclusion