API Security
Security Principles
- Authentication: Verify identity
- Authorization: Control access
- Encryption: Protect data in transit
- Validation: Sanitize inputs
- Rate Limiting: Prevent abuse
- Monitoring: Detect threats
1. HTTPS Only
// Redirect HTTP to HTTPS
app.use((req, res, next) => {
if (!req.secure && process.env.NODE_ENV === 'production') {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
// Strict Transport Security
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
next();
});2. JWT Authentication
const jwt = require('jsonwebtoken');
// Generate token
function generateToken(user) {
return jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
}
// Verify token
const authenticate = (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};3. Input Validation
const Joi = require('joi');
const validator = require('validator');
// Schema validation
const userSchema = Joi.object({
name: Joi.string().min(2).max(100).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required()
});
app.post('/api/users', async (req, res) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(422).json({ error: error.details[0].message });
}
// Sanitize inputs
const sanitizedData = {
name: validator.escape(req.body.name),
email: validator.normalizeEmail(req.body.email),
password: req.body.password
};
const user = await User.create(sanitizedData);
res.status(201).json(user);
});4. SQL Injection Prevention
// ❌ Bad - Vulnerable to SQL injection
const query = `SELECT * FROM users WHERE email = '${req.body.email}'`;
// ✅ Good - Use parameterized queries
const query = 'SELECT * FROM users WHERE email = ?';
const users = await db.query(query, [req.body.email]);
// ✅ Good - Use ORM
const user = await User.findOne({ email: req.body.email });5. XSS Prevention
const helmet = require('helmet');
const xss = require('xss-clean');
// Helmet for security headers
app.use(helmet());
// XSS protection
app.use(xss());
// Content Security Policy
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"]
}
}));6. CSRF Protection
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(csrf({ cookie: true }));
app.get('/form', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
app.post('/api/users', (req, res) => {
// CSRF token automatically validated
const user = await User.create(req.body);
res.status(201).json(user);
});7. Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: 'Too many requests from this IP'
});
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
skipSuccessfulRequests: true
});
app.use('/api', limiter);
app.post('/api/auth/login', authLimiter, login);8. Password Security
const bcrypt = require('bcrypt');
// Hash password
async function hashPassword(password) {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash(password, salt);
}
// Verify password
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
// User model
userSchema.pre('save', async function(next) {
if (this.isModified('password')) {
this.password = await hashPassword(this.password);
}
next();
});9. API Keys
const crypto = require('crypto');
// Generate API key
function generateApiKey() {
return crypto.randomBytes(32).toString('hex');
}
// Validate API key
const validateApiKey = async (req, res, next) => {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
const validKey = await ApiKey.findOne({ key: apiKey, active: true });
if (!validKey) {
return res.status(401).json({ error: 'Invalid API key' });
}
req.apiKey = validKey;
next();
};10. CORS Security
const cors = require('cors');
app.use(cors({
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));11. Secrets Management
// ❌ Bad - Hardcoded secrets
const JWT_SECRET = 'my-secret-key';
// ✅ Good - Environment variables
const JWT_SECRET = process.env.JWT_SECRET;
// ✅ Better - Use secrets manager
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();
async function getSecret(secretName) {
const data = await secretsManager.getSecretValue({ SecretId: secretName }).promise();
return JSON.parse(data.SecretString);
}12. Audit Logging
const auditLog = async (req, res, next) => {
const log = {
timestamp: new Date(),
userId: req.user?.id,
action: `${req.method} ${req.path}`,
ip: req.ip,
userAgent: req.headers['user-agent']
};
res.on('finish', async () => {
log.statusCode = res.statusCode;
await AuditLog.create(log);
});
next();
};
app.use(auditLog);13. Dependency Security
# Check for vulnerabilities
npm audit
# Fix vulnerabilities
npm audit fix
# Use Snyk
npm install -g snyk
snyk test
snyk monitor14. Error Handling
// Don't expose internal errors
app.use((err, req, res, next) => {
console.error(err.stack);
// ❌ Bad - Exposes stack trace
// res.status(500).json({ error: err.stack });
// ✅ Good - Generic error message
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'An error occurred'
}
});
});15. Security Headers
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
},
frameguard: {
action: 'deny'
},
noSniff: true,
xssFilter: true
}));.NET Security
// Authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidateAudience = true
};
});
// CORS
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder.WithOrigins("https://example.com")
.AllowAnyMethod()
.AllowAnyHeader());
});
// Data protection
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(@"./keys/"))
.ProtectKeysWithCertificate(certificate);
// Anti-forgery
services.AddAntiforgery(options =>
{
options.HeaderName = "X-CSRF-TOKEN";
});Interview Tips
- Explain HTTPS: Encrypt data in transit
- Show authentication: JWT, API keys
- Demonstrate validation: Input sanitization
- Discuss injection: SQL, XSS prevention
- Mention rate limiting: Prevent abuse
- Show headers: Security headers with Helmet
Summary
API security requires HTTPS, strong authentication (JWT, OAuth), input validation, SQL injection prevention, XSS protection, CSRF tokens, rate limiting, password hashing, secure API keys, CORS configuration, secrets management, audit logging, dependency scanning, proper error handling, and security headers. Use Helmet for Node.js security. Implement defense in depth. Essential for production REST APIs.
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.