Node.js Advanced: Security, Performance and Production
Authentication and Security
In this lesson you will
- Hash and verify passwords with scrypt
- Compare sessions and tokens for authentication
- Apply a checklist of common API security measures
Introduction
An API that works is not the same as an API that is safe. Real services face constant automated attacks, so security is part of the design from the start. This lesson covers the essentials. Treat it as a starting point and follow the current OWASP guidance for anything you put on the internet.
Never store plain passwords
When someone signs up, store a salted hash of the password made with a slow algorithm designed for passwords. Node's built-in crypto module provides scrypt:
Real output:
64
true
false- The salt is random per user, so equal passwords get different hashes.
- Store the salt and hash (for example as hex text), never the password.
timingSafeEqualcompares without leaking timing information.scryptSyncblocks the event loop while it works. In a busy server, use the asynchronousscryptfunction so other requests are not delayed.- Many teams prefer a well-reviewed library for Argon2id or bcrypt. Use current recommended settings.
Quick quiz
Why add a random salt before hashing each password?
Sessions or tokens?
After logging in, the server needs to recognise the user on later requests.
- Server-side sessions: the server stores the session and gives the browser a random ID in a cookie. Easy to revoke, and a good default for websites. Use
HttpOnly,SecureandSameSitecookie flags. - Tokens (such as JSON Web Tokens): a signed token the client sends with each request, often in an
Authorizationheader. Useful for APIs used by apps and several services, but harder to revoke, so keep them short-lived and verify signature, expiry and audience on every request.
Whichever you choose, use a well-maintained library, keep secrets out of code, and protect login pages from abuse.
A security checklist
- HTTPS everywhere, terminated by a trusted proxy or platform.
- Validate all input with schemas, as in the intermediate course.
- Parameterised database queries only.
- Security headers using a library such as Helmet, which sets sensible defaults.
- Rate limiting on login and expensive routes, to slow brute-force and scraping.
- CORS: allow only the origins that need access, never
*for credentialed requests. - Limit body sizes and request timeouts.
- Do not leak errors: log details privately and return generic messages.
- Authorisation checks on every request: confirm the user may access that resource, not only that they are logged in.
- Secrets in environment variables or a secrets manager, never in Git.
- Keep dependencies updated and run
npm audit. - Least privilege: run as a non-root user with minimal permissions, and give the database account only what it needs.
- Log security events, such as failed logins, without logging passwords or tokens.
Common mistakes
- Using
Math.random()for tokens. Usecrypto.randomUUID()orrandomBytes. - Trusting IDs in URLs without checking ownership.
- Returning whole database rows, including password hashes, in JSON.
- Disabling certificate checks "to make it work".
- Committing a
.envfile.
Try it yourself
Add registration and login to your to-do API: hash passwords with scrypt, store users in SQLite, give each todo an owner, and make every todo route check that the logged-in user owns the item. Add rate limiting to the login route.
Summary
- Store passwords as salted scrypt (or Argon2 or bcrypt) hashes and compare with a timing-safe function.
- Choose sessions or short-lived tokens, and verify authorisation on every request.
- Work through a checklist: HTTPS, validation, parameterised queries, headers, rate limits, safe errors, secrets and updates.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.
Sources and further reading