How do you mitigate SQL/NoSQL injection attacks in a Node.js API?
Answer: Use ORM/ODM libraries like Sequelize or Mongoose that sanitize inputs. Validate and sanitize user input using libraries like
express-validator
.
Explain JWT authentication and how to securely store tokens.
Answer: JWTs are signed tokens for stateless authentication. Store them in HTTPOnly cookies to prevent XSS attacks and use refresh tokens for renewing access tokens.
How would you implement rate limiting and why is it important?
Answer: Use
express-rate-limit
to restrict repeated requests, preventing DDoS/brute-force attacks. Example:const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use(limiter);
What security headers does Helmet.js set, and why are they critical?
Answer: Helmet sets headers like
Content-Security-Policy
,X-Frame-Options
, andStrict-Transport-Security
to protect against XSS, clickjacking, and enforce HTTPS.
How do you manage sensitive configuration data (e.g., API keys) in Node.js?
Answer: Use environment variables with
dotenv
and never commit.env
files. Store secrets in secure vaults (e.g., AWS Secrets Manager) in production.
Example Scenario: Building a Secure Node.js API
Step 1: Initialize Project & Dependencies
npm init -y npm install express helmet bcrypt jsonwebtoken cors express-rate-limit express-validator mongoose cookie-parser
Step 2: Configure HTTPS
Use Let’s Encrypt for production. For development, generate a self-signed certificate:
const https = require('https'); const fs = require('fs'); const options = { key: fs.readFileSync('server.key'), cert: fs.readFileSync('server.cert') }; const server = https.createServer(options, app);
Step 3: Security Middleware
const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const rateLimit = require('express-rate-limit'); const app = express(); // Middleware app.use(helmet()); app.use(cors({ origin: 'https://trusted-domain.com' })); app.use(express.json({ limit: '10kb' })); // Prevent large payloads app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })); // Rate limiting
Step 4: Database Setup (MongoDB/Mongoose)
const mongoose = require('mongoose'); mongoose.connect(process.env.DB_URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('DB Connected!')) .catch(err => console.error('DB Connection Error:', err)); // User model with bcrypt password hashing const userSchema = new mongoose.Schema({ email: { type: String, unique: true }, password: String, role: { type: String, enum: ['user', 'admin'] } }); userSchema.pre('save', async function (next) { if (this.isModified('password')) { this.password = await bcrypt.hash(this.password, 12); } next(); });
Step 5: Authentication with JWT & HTTPOnly Cookies
const jwt = require('jsonwebtoken'); const cookieParser = require('cookie-parser'); app.use(cookieParser()); // Login route app.post('/login', async (req, res) => { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user || !(await bcrypt.compare(password, user.password))) { return res.status(401).json({ error: 'Invalid credentials' }); } const accessToken = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '15m' }); const refreshToken = jwt.sign({ id: user._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' }); res.cookie('accessToken', accessToken, { httpOnly: true, secure: true, sameSite: 'Strict' }); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'Strict' }); res.json({ role: user.role }); });
Step 6: Input Validation
const { body, validationResult } = require('express-validator'); app.post('/register', [ body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 8 }).trim().escape() ], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() }); // Proceed with registration } );
Step 7: Role-Based Authorization Middleware
const authorize = (roles) => (req, res, next) => { const token = req.cookies.accessToken; if (!token) return res.status(403).json({ error: 'Access denied' }); jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err || !roles.includes(user.role)) return res.status(403).json({ error: 'Unauthorized' }); req.user = user; next(); }); }; // Admin-only route app.get('/admin', authorize(['admin']), (req, res) => { res.json({ message: 'Admin access granted' }); });
Step 8: Error Handling & Logging
// Central error handler app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Internal server error' }); }); // Logging middleware (use Winston or Morgan in production) app.use((req, res, next) => { console.log(`${req.method} ${req.path}`); next(); });
Step 9: CSRF Protection (Optional)
For APIs, use the SameSite=Strict
cookie attribute. For forms, use packages like csurf
:
const csrf = require('csurf'); const csrfProtection = csrf({ cookie: true }); app.use(csrfProtection);
Key Security Takeaways:
HTTPS: Encrypt data in transit.
Helmet.js: Secure HTTP headers.
JWT in HTTPOnly Cookies: Mitigate XSS risks.
Rate Limiting: Prevent brute-force attacks.
Input Validation/Sanitization: Block injection attacks.
Role-Based Access Control (RBAC): Least privilege principle.
Environment Variables: Keep secrets out of code.
Logging & Monitoring: Detect anomalies early.
No comments:
Post a Comment