# Building Secure APIs with Node.js

**Author:** kelexine  
**Date:** 2024-12-01  
**Category:** Security  
**Tags:** Node.js, Security, API, JWT, Express  
**URL:** https://kelexine.is-a.dev/blog/secure-apis-nodejs

---

# Building Secure APIs with Node.js

In today's interconnected digital landscape, API security is not just a best practice—it's a fundamental requirement. As developers, we have a responsibility to protect our users' data and ensure the integrity of our systems. This guide will walk you through implementing robust security measures in your Node.js APIs.

## Why API Security Matters

Before diving into implementation details, let's understand why API security is crucial:

- **Data Protection**: APIs often handle sensitive user information
- **System Integrity**: Prevents unauthorized access and malicious activities  
- **Compliance**: Meets regulatory requirements (GDPR, HIPAA, etc.)
- **Business Continuity**: Protects against attacks that could disrupt services

## Setting Up the Foundation

### 1. Environment Configuration

First, let's establish secure environment management:

```javascript
// config/security.js
require('dotenv').config();

module.exports = {
  jwtSecret: process.env.JWT_SECRET,
  jwtExpire: process.env.JWT_EXPIRE || '1h',
  bcryptRounds: parseInt(process.env.BCRYPT_ROUNDS) || 12,
  rateLimitWindow: parseInt(process.env.RATE_LIMIT_WINDOW) || 900000, // 15 minutes
  rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX) || 100
};
```

### 2. Input Validation

Never trust user input. Always validate and sanitize:

```javascript
// middleware/validation.js
const { body, validationResult } = require('express-validator');

const validateRegistration = [
  body('email')
    .isEmail()
    .normalizeEmail()
    .withMessage('Please provide a valid email'),
  body('password')
    .isLength({ min: 8 })
    .matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
    .withMessage('Password must contain uppercase, lowercase, number and special character'),
  body('name')
    .trim()
    .isLength({ min: 2, max: 50 })
    .matches(/^[a-zA-Z\s]+$/)
    .withMessage('Name must contain only letters and spaces')
];

const handleValidationErrors = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({
      success: false,
      message: 'Validation failed',
      errors: errors.array()
    });
  }
  next();
};

module.exports = { validateRegistration, handleValidationErrors };
```

## Authentication & Authorization

### JWT Implementation

Implement secure JWT-based authentication:

```javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
const { jwtSecret, jwtExpire } = require('../config/security');

const generateToken = (payload) => {
  return jwt.sign(payload, jwtSecret, { 
    expiresIn: jwtExpire,
    issuer: 'your-app-name'
  });
};

const verifyToken = (req, res, next) => {
 const token = req.header('Authorization')?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ 
      success: false, 
      message: 'Access denied. No token provided.' 
    });
  }

  try {
    const decoded = jwt.verify(token, jwtSecret);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ 
      success: false, 
      message: 'Invalid token.' 
    });
  }
};

module.exports = { generateToken, verifyToken };
```

## Rate Limiting

Protect against brute force attacks:

```javascript
// middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');
const { rateLimitWindow, rateLimitMax } = require('../config/security');

const createRateLimiter = (windowMs, max, message) => {
  return rateLimit({
    windowMs,
    max,
    message: {
      success: false,
      message: message || 'Too many requests from this IP'
    },
    standardHeaders: true,
    legacyHeaders: false,
    skip: (req) => {
      // Skip rate limiting for authenticated users
      return req.user && req.user.role === 'admin';
    }
  });
};

// Different rate limits for different endpoints
const generalLimiter = createRateLimiter(rateLimitWindow, rateLimitMax);
const authLimiter = createRateLimiter(15 * 60 * 1000, 5, 'Too many authentication attempts');
const passwordResetLimiter = createRateLimiter(60 * 60 * 1000, 3, 'Too many password reset attempts');

module.exports = { generalLimiter, authLimiter, passwordResetLimiter };
```

## Security Headers

Implement security headers using Helmet:

```javascript
// app.js
const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));
```

## Error Handling

Never expose sensitive information in errors:

```javascript
// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
  // Log error details (in production, use proper logging service)
  console.error(err.stack);

  // Default error
  let error = {
    success: false,
    message: 'Internal Server Error'
  };

  // Mongoose bad ObjectId
  if (err.name === 'CastError') {
    error.message = 'Resource not found';
    return res.status(404).json(error);
  }

  // Mongoose duplicate key
  if (err.code === 11000) {
    error.message = 'Duplicate field value entered';
    return res.status(400).json(error);
  }

  // Mongoose validation error
  if (err.name === 'ValidationError') {
    error.message = Object.values(err.errors).map(e => e.message).join(', ');
    return res.status(400).json(error);
  }

  // JWT errors
  if (err.name === 'JsonWebTokenError') {
    error.message = 'Invalid token';
    return res.status(401).json(error);
  }

  if (err.name === 'TokenExpiredError') {
    error.message = 'Token expired';
    return res.status(401).json(error);
  }

  res.status(err.statusCode || 500).json(error);
};

module.exports = errorHandler;
```

## Conclusion

Building secure APIs is an ongoing process that requires constant vigilance and updates. The practices covered in this guide form a solid foundation, but security is a moving target. Stay informed about the latest threats, keep your dependencies updated, and always prioritize security in your development process.

> **Remember:** Security is not a feature, it's a requirement.

---

*Next up: Implementing OAuth 2.0 and social login integration...*

---

*This content is available at [kelexine.is-a.dev/blog/secure-apis-nodejs](https://kelexine.is-a.dev/blog/secure-apis-nodejs)*
