API Security

Security Principles

  1. Authentication: Verify identity
  2. Authorization: Control access
  3. Encryption: Protect data in transit
  4. Validation: Sanitize inputs
  5. Rate Limiting: Prevent abuse
  6. 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 monitor

14. 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.

Test Your Restful-api Knowledge

Ready to put your skills to the test? Take our interactive Restful-api quiz and get instant feedback on your answers.