Your resource for web content, online publishing
and the distribution of digital products.
«  
  »
S M T W T F S
 
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
10
 
11
 
12
 
13
 
14
 
15
 
16
 
17
 
18
 
19
 
20
 
21
 
22
 
23
 
24
 
25
 
26
 
27
 
28
 
29
 
30
 
 
 
 

The Developer’s Guide to Bulletproof API Security in Node.js

DATE POSTED:March 4, 2025
Security Things That Matter

Security is dull & boring until you get hacked. Then it's REALLY interesting. Node is great at making it easy to create APIs overnight, but that also makes it easy to do it wrong.

\ I have seen others get compromised because of:

  • They employed packages they never audited (npm is wonderful & frightening)
  • They rely on input from users (never, EVER do that)
  • They hard-code credentials directly into the code (why in the world?)

\ I am sure you have wondered what you'd do if someone hacked your app. Let’s look at some suggestions to avoid getting hacked.

The Basics
  • Check What Users/Customers Send You

    Always expect users to attempt strange things. Check ALL OF IT.

    \

// Bad code app.post('/users', (req, res) => { db.users.create(req.body); // Accepting whatever users send? Bad idea! }); // Better approach app.post('/users', (req, res) => { // Use something like Joi or express-validator if (!req.body.email || !req.body.email.includes('@')) { return res.status(400).send('Invalid email'); } if (typeof req.body.age !== 'number') { return res.status(400).send('Age must be a number'); } // Now it's safer to save db.users.create(req.body); });

\ I found this out for myself when someone crashed my application by placing an emoji within the username field. Fun times.

  • Login Stuff: Don't Mess This Up

    JWT tokens are cool but simple to get wrong. Here's my take:

    \

// Creating tokens - keep them short lived! const token = jwt.sign( { userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' } // Dont make these last forever ); // Check tokens on protected routes function checkAuth(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).send('Login required'); } try { const user = jwt.verify(token, process.env.JWT_SECRET); req.user = user; // Add user info to request next(); } catch (err) { return res.status(403).send('Invalid or expired token'); } } // Use it to protect routes app.get('/profile', checkAuth, (req, res) => { // Only logged in users get here });

\ Auth has two halves: ensuring that the user is who they say they are (authentication) and ensuring that they can do what they're trying to do (authorization). \n

  • Never, under any circumstances, commit your database password to GitHub

    \

// NO NO NO - don't hardcode passwords!! const db = mysql.connect({ host: 'mydatabase.server.com', user: 'admin', password: 'SuperSecret123!' // This should never be in your code }); // Do this instead require('dotenv').config(); const db = mysql.connect({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD });

\ And DO NOT forget to place your .env file to .gitignore.

Real Security Problems I've Seen
  • SQL Injection Still Works?! Yes, it does. And it is so easy to stop:

    \

// Dangerous - allows SQL injection app.get('/users', (req, res) => { const name = req.query.name; db.query(`SELECT * FROM users WHERE name = '${name}'`, // BAD! (err, results) => res.json(results) ); }); // Safe - use parameters app.get('/users', (req, res) => { const name = req.query.name; db.query('SELECT * FROM users WHERE name = ?', [name], // This prevents SQL injection (err, results) => res.json(results) ); });

\ When someone attempts ?name=x'; DROP TABLE users; -- you will be happy you utilized parameters.

\

  • Too Many Requests = Crashed Server

    The app crashed when a user abused the search API too much. Implement rate limiting:

\

const rateLimit = require('express-rate-limit'); // Basic protection for all routes const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit per IP }); app.use(limiter); // Extra protection for login attempts const loginLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 5 // 5 login attempts per hour }); app.use('/login', loginLimiter);

\

  • Old Packages = Security Holes

    Most hacks happen through outdated packages. Check yours:

# Run this often! npm audit # Fix what you can npm audit fix

\

Use Helmet for HTTP Headers

One line of code that fixes several issues:

const helmet = require('helmet'); app.use(helmet()); // Adds security headers FAQs that every Developer should know

Q: What do you need to fix first that is most important?

A: Input validation. Most attacks start there.

\ Q: What is the best way to know if my API security is sufficient?

A: Have someone attempt to break it. Or you can try a tool like OWASP ZAP.

\ Q: How can I prevent security vulnerabilities caused by dependencies? \n A: Schedule a calendar event to update your dependencies in a timely manner. To detect these vulnerabilities, consider using tools like npm audit, snyk, or dependable.

\ Q: What is the best way to know if my API security is sufficient?

A: Have someone attempt to break it. Or you can try a tool like OWASP ZAP.

Final Thoughts - Lessons to learn from

Security is not an afterthought - it should be built into the code from the outset.

\ Start with these basics:

  • Authenticate all users
  • Secure your auth routes
  • Store Secrets in Environment Variables
  • Update dependencies
  • Set rate limits
  • Always use HTTPS

\ So, which gap will you be closing today?

\