On this page
Node.js Security
Security must be built in from the start, not added later.
Essential Packages
npm install helmet cors express-rate-limit bcrypt jsonwebtoken
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';
app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100
});
app.use('/api/', limiter);
Password Hashing
Never store plain-text passwords:
import bcrypt from 'bcrypt';
// Register
const salt = await bcrypt.genSalt(12);
const hashedPassword = await bcrypt.hash(password, salt);
// Login
const isValid = await bcrypt.compare(inputPassword, user.password);
JWT Authentication
import jwt from 'jsonwebtoken';
function generateToken(userId) {
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '7d' });
}
function verifyToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'No token' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
}
Input Validation
Always validate and sanitize user input:
import { body, validationResult } from 'express-validator';
app.post('/register',
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
// Proceed...
}
);
SQL/NoSQL Injection Prevention
Use parameterized queries — ORMs like Prisma and Mongoose handle this:
// BAD — never do this
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// GOOD — use ORM
await User.findOne({ email });
Security Checklist
- Use HTTPS in production
- Store secrets in environment variables
- Hash passwords with bcrypt (cost factor ≥ 12)
- Validate all input
- Rate limit auth endpoints
- Set secure cookie flags (
httpOnly,secure,sameSite) - Keep dependencies updated (
npm audit) - Don’t expose stack traces in production
- Implement proper CORS policy
- Use helmet for security headers
See also Security in JavaScript for client-side security topics.
Environment Variable Safety
Never hardcode secrets:
// .env (not committed to git)
JWT_SECRET=your-256-bit-secret
DATABASE_URL=postgres://user:pass@localhost/db
// app.js
import 'dotenv/config';
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('JWT_SECRET is required');
Add .env to .gitignore and provide .env.example with placeholder keys.
Dependency Auditing
npm audit
npm audit fix
npx snyk test
Run audits in CI and block merges on critical vulnerabilities.
Secure HTTP Headers with Helmet
Helmet sets headers that mitigate common attacks:
X-Content-Type-Options: nosniffStrict-Transport-Security(HSTS)X-Frame-Options: DENY(clickjacking protection)
Session Security with express-session
import session from 'express-session';
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000
}
}));
Logging Without Leaking Secrets
// Bad
console.log('Login attempt', { email, password });
// Good
console.log('Login attempt', { email, success: false });
Redact passwords, tokens, and PII from logs.