Secure coding practices are fundamental to building safe applications. This article covers error handling, logging, secrets management, and security best practices.
Introduction
Secure coding practices provide:
- Vulnerability prevention
- Error handling
- Information security
- Code quality
- Compliance
Understanding these practices helps you:
- Write secure code
- Handle errors safely
- Manage secrets
- Prevent information leaks
- Build reliable systems
Error Handling
Safe Error Handling
// ✅ Good: Safe error handling
function processUserData(data) {
try {
if (!data || typeof data !== 'object') {
throw new Error('Invalid data format');
}
// Process data
return { success: true, data };
} catch (error) {
// Log error securely (don't expose details to user)
console.error('Error processing data:', error.message);
// Return generic error to user
return {
success: false,
error: 'An error occurred while processing your request'
};
}
}
// Usage
const result = processUserData({ name: 'John' });
console.log(result); // { success: true, data: { name: 'John' } }
Error Logging
// ✅ Good: Secure error logging
class SecureLogger {
static log(level, message, context = {}) {
// Remove sensitive data
const sanitized = this.sanitizeContext(context);
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
context: sanitized
};
// Log to secure location (not console in production)
if (process.env.NODE_ENV === 'production') {
this.sendToLoggingService(logEntry);
} else {
console.log(JSON.stringify(logEntry));
}
}
static sanitizeContext(context) {
const sensitiveKeys = ['password', 'token', 'secret', 'apiKey', 'creditCard'];
const sanitized = { ...context };
for (const key of sensitiveKeys) {
if (key in sanitized) {
sanitized[key] = '[REDACTED]';
}
}
return sanitized;
}
static sendToLoggingService(logEntry) {
// Send to secure logging service (e.g., Sentry, LogRocket)
// fetch('/api/logs', { method: 'POST', body: JSON.stringify(logEntry) });
}
static error(message, error, context = {}) {
this.log('ERROR', message, { ...context, error: error.message });
}
static warn(message, context = {}) {
this.log('WARN', message, context);
}
static info(message, context = {}) {
this.log('INFO', message, context);
}
}
// Usage
SecureLogger.error('User login failed', new Error('Invalid credentials'), {
username: 'john',
password: 'secret123' // Will be redacted
});
Custom Error Classes
// ✅ Good: Custom error classes
class ApplicationError extends Error {
constructor(message, statusCode = 500, details = {}) {
super(message);
this.name = 'ApplicationError';
this.statusCode = statusCode;
this.details = details;
}
toJSON() {
return {
error: this.message,
statusCode: this.statusCode
};
}
}
class ValidationError extends ApplicationError {
constructor(message, details = {}) {
super(message, 400, details);
this.name = 'ValidationError';
}
}
class AuthenticationError extends ApplicationError {
constructor(message = 'Authentication failed') {
super(message, 401);
this.name = 'AuthenticationError';
}
}
class AuthorizationError extends ApplicationError {
constructor(message = 'Access denied') {
super(message, 403);
this.name = 'AuthorizationError';
}
}
// Usage
try {
throw new ValidationError('Invalid email format', { field: 'email' });
} catch (error) {
console.log(error.toJSON());
// { error: 'Invalid email format', statusCode: 400 }
}
Secrets Management
Environment Variables
// ✅ Good: Use environment variables for secrets
// .env file (never commit to version control)
// DATABASE_URL=postgresql://user:password@localhost/db
// API_KEY=secret_key_here
// JWT_SECRET=jwt_secret_here
// Load environment variables
require('dotenv').config();
const dbURL = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
const jwtSecret = process.env.JWT_SECRET;
// Validate required secrets
function validateSecrets() {
const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
for (const secret of required) {
if (!process.env[secret]) {
throw new Error(`Missing required environment variable: ${secret}`);
}
}
}
validateSecrets();
Secrets Vault
// ✅ Good: Use secrets vault (e.g., HashiCorp Vault, AWS Secrets Manager)
class SecretsManager {
constructor() {
this.cache = new Map();
this.cacheTTL = 3600000; // 1 hour
}
async getSecret(secretName) {
// Check cache
const cached = this.cache.get(secretName);
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
return cached.value;
}
// Fetch from vault
const secret = await this.fetchFromVault(secretName);
// Cache result
this.cache.set(secretName, {
value: secret,
timestamp: Date.now()
});
return secret;
}
async fetchFromVault(secretName) {
// Example: AWS Secrets Manager
// const client = new SecretsManagerClient();
// const response = await client.getSecretValue({ SecretId: secretName });
// return response.SecretString;
// Placeholder
return process.env[secretName];
}
clearCache() {
this.cache.clear();
}
}
// Usage
const secretsManager = new SecretsManager();
const apiKey = await secretsManager.getSecret('api_key');
Secure Configuration
// ✅ Good: Secure configuration management
class Config {
static get(key, defaultValue = null) {
const value = process.env[key];
if (!value && defaultValue === null) {
throw new Error(`Configuration key not found: ${key}`);
}
return value || defaultValue;
}
static getSecret(key) {
const value = process.env[key];
if (!value) {
throw new Error(`Secret not found: ${key}`);
}
return value;
}
static getBoolean(key, defaultValue = false) {
const value = process.env[key];
return value ? value.toLowerCase() === 'true' : defaultValue;
}
static getNumber(key, defaultValue = 0) {
const value = process.env[key];
return value ? parseInt(value, 10) : defaultValue;
}
}
// Usage
const dbURL = Config.getSecret('DATABASE_URL');
const port = Config.getNumber('PORT', 3000);
const debug = Config.getBoolean('DEBUG', false);
Dependency Management
Dependency Scanning
// ✅ Good: Scan dependencies for vulnerabilities
// npm audit
// npm audit fix
// npm audit fix --force
// Automated scanning in CI/CD
// npm install -g snyk
// snyk test
// snyk monitor
class DependencyChecker {
static async checkVulnerabilities() {
// Use npm audit API
const { execSync } = require('child_process');
try {
const result = execSync('npm audit --json', { encoding: 'utf-8' });
const audit = JSON.parse(result);
if (audit.vulnerabilities && Object.keys(audit.vulnerabilities).length > 0) {
console.warn('Vulnerabilities found:');
console.warn(JSON.stringify(audit.vulnerabilities, null, 2));
return false;
}
return true;
} catch (error) {
console.error('Error checking vulnerabilities:', error.message);
return false;
}
}
}
// Usage
const isSecure = await DependencyChecker.checkVulnerabilities();
Dependency Pinning
// ✅ Good: Pin dependencies to specific versions
// package.json
{
"dependencies": {
"express": "4.18.2",
"lodash": "4.17.21"
},
"devDependencies": {
"jest": "29.5.0"
}
}
// Use package-lock.json to lock transitive dependencies
// npm ci (instead of npm install) to use locked versions
Input Sanitization
Sanitize User Input
// ✅ Good: Comprehensive input sanitization
class InputSanitizer {
static sanitize(input, type = 'text') {
switch (type) {
case 'text':
return this.sanitizeText(input);
case 'email':
return this.sanitizeEmail(input);
case 'url':
return this.sanitizeURL(input);
case 'number':
return this.sanitizeNumber(input);
default:
return input;
}
}
static sanitizeText(text) {
return String(text)
.trim()
.replace(/[<>]/g, '')
.substring(0, 1000);
}
static sanitizeEmail(email) {
return String(email)
.trim()
.toLowerCase()
.substring(0, 254);
}
static sanitizeURL(url) {
try {
const parsed = new URL(url);
if (!['http:', 'https:'].includes(parsed.protocol)) {
return null;
}
return parsed.toString();
} catch {
return null;
}
}
static sanitizeNumber(num) {
const parsed = parseFloat(num);
return isNaN(parsed) ? 0 : parsed;
}
}
// Usage
const email = InputSanitizer.sanitize(' [email protected] ', 'email');
console.log(email); // '[email protected]'
Rate Limiting
Implement Rate Limiting
// ✅ Good: Rate limiting to prevent abuse
class RateLimiter {
constructor(maxRequests = 100, windowMs = 60000) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = new Map();
}
isAllowed(identifier) {
const now = Date.now();
const userRequests = this.requests.get(identifier) || [];
// Remove old requests outside the window
const recentRequests = userRequests.filter(
time => now - time < this.windowMs
);
if (recentRequests.length >= this.maxRequests) {
return false;
}
// Add current request
recentRequests.push(now);
this.requests.set(identifier, recentRequests);
return true;
}
getRemainingRequests(identifier) {
const userRequests = this.requests.get(identifier) || [];
return Math.max(0, this.maxRequests - userRequests.length);
}
}
// Usage
const limiter = new RateLimiter(10, 60000); // 10 requests per minute
function handleRequest(userId) {
if (!limiter.isAllowed(userId)) {
return { error: 'Rate limit exceeded' };
}
// Process request
return { success: true };
}
Security Headers
Set Security Headers
// ✅ Good: Set security headers (server-side)
// Express.js example
const express = require('express');
const app = express();
// Content Security Policy
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com"
);
next();
});
// X-Content-Type-Options
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
next();
});
// X-Frame-Options
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
// X-XSS-Protection
app.use((req, res, next) => {
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// Strict-Transport-Security
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
// Referrer-Policy
app.use((req, res, next) => {
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
});
Practical Security Examples
Secure API Endpoint
// ✅ Good: Secure API endpoint
async function handleUserRequest(req, res) {
try {
// 1. Validate input
const { email, password } = req.body;
if (!email || !password) {
throw new ValidationError('Email and password are required');
}
// 2. Sanitize input
const sanitizedEmail = InputSanitizer.sanitize(email, 'email');
// 3. Check rate limit
if (!rateLimiter.isAllowed(req.ip)) {
throw new ApplicationError('Too many requests', 429);
}
// 4. Authenticate
const user = await authenticateUser(sanitizedEmail, password);
if (!user) {
throw new AuthenticationError();
}
// 5. Authorize
if (!user.isActive) {
throw new AuthorizationError('User account is inactive');
}
// 6. Process request
const result = await processUserData(user);
// 7. Return response
res.json({ success: true, data: result });
} catch (error) {
// 8. Handle error securely
SecureLogger.error('Request failed', error, { ip: req.ip });
const statusCode = error.statusCode || 500;
const message = error.statusCode ? error.message : 'Internal server error';
res.status(statusCode).json({ error: message });
}
}
Best Practices
- Never expose sensitive information:
// ❌ Bad console.log('Password:', password); // ✅ Good console.log('Authentication attempt'); ```javascript - Use HTTPS everywhere:
// ✅ Good // All communication over HTTPS // ❌ Bad // HTTP connections allowed ```javascript - Validate and sanitize all input:
// ✅ Good const sanitized = InputSanitizer.sanitize(userInput); // ❌ Bad const data = userInput; ```javascript
Common Mistakes
- Logging sensitive data:
// ❌ Bad console.log('User:', { email, password }); // ✅ Good console.log('User login attempt'); ```javascript - Hardcoding secrets:
// ❌ Bad const apiKey = 'sk_live_abc123'; // ✅ Good const apiKey = process.env.API_KEY; ```javascript - Not validating input:
// ❌ Bad const user = await findUser(userId); // ✅ Good if (!userId || typeof userId !== 'number') { throw new ValidationError('Invalid user ID'); } const user = await findUser(userId);
Summary
Secure coding practices are essential. Key takeaways:
- Handle errors safely
- Log securely
- Manage secrets
- Scan dependencies
- Sanitize input
- Implement rate limiting
- Set security headers
- Validate all input
Related Resources
Next Steps
- Learn about Dependency Security
- Explore Authentication and Authorization
- Study Security Testing
- Practice secure coding
- Implement security headers
Comments