API Security

Infrastructure & Governance Security Notes Jan 9, 2025 JAVASCRIPT
security authentication authorization encryption owasp

Definition

API security is the comprehensive discipline of protecting APIs from unauthorized access, data breaches, injection attacks, abuse, and other threats throughout their entire lifecycle - from design and development to deployment and retirement. It encompasses authentication (proving who you are), authorization (what you’re allowed to do), encryption (protecting data in transit and at rest), input validation, rate limiting, monitoring, and incident response. Unlike web application security, API security must handle machine-to-machine communication, token-based authentication, and programmatic attacks at scale.

The challenge of API security is that APIs are designed to be accessible - often publicly - making them prime targets for attackers. While a website might have a human-friendly login form with CAPTCHA, APIs must serve millions of automated requests per second. This creates a massive attack surface where vulnerabilities can be exploited at machine speed. A single broken authentication endpoint can leak millions of records in minutes.

API security is not a single tool or technique; it’s a layered defense strategy. You need authentication to prove identity, authorization to enforce permissions, encryption to protect sensitive data, rate limiting to prevent abuse, input validation to stop injection attacks, logging to detect breaches, and incident response to contain damage. Skip any layer and you create an exploitable weakness. The OWASP API Security Top 10 lists the most critical vulnerabilities that organizations must address.

Example

Broken Object Level Authorization (BOLA): Uber’s API allowed any authenticated user to access any trip receipt by changing the trip ID in the URL (/api/trips/12345). Attackers enumerated millions of IDs, collecting receipts containing names, addresses, and phone numbers. Fix: Check that req.user.id === trip.user_id before returning data.

Broken Authentication: Peloton’s API exposed user data through an unauthenticated endpoint. Anyone could access account details, workout history, and private profile information without logging in. Fix: Require authentication on all non-public endpoints and validate tokens on every request.

Excessive Data Exposure: Facebook’s Graph API returned full user objects (email, phone, friends list) even when the client only requested name and profile picture. Attackers collected massive datasets by making minimal requests. Fix: Implement field-level authorization and only return requested fields.

Lack of Rate Limiting: Parler’s API had no rate limits, allowing researchers to scrape 99% of posts (70TB of data) including deleted content and GPS locations. Fix: Implement rate limiting per user, per IP, and per endpoint with different limits for read vs. write operations.

Mass Assignment: GitHub’s API once allowed users to escalate privileges by adding "admin": true to their profile update request. The API blindly accepted all JSON fields without validation. Fix: Use allowlists for accepted fields and never trust client input.

SQL Injection: An e-commerce API with search functionality: /api/products?query=laptop was vulnerable because it directly inserted the query parameter into SQL. Attackers injected ' OR 1=1-- to dump the entire database. Fix: Use parameterized queries or ORMs that escape inputs.

Analogy

The Medieval Castle Defense: A castle has multiple layers of security - moat (network firewall), walls (authentication), gates with guards (authorization), watchtowers (monitoring), and vaults for treasure (encryption). Attackers must breach every layer. API security works the same way: WAF β†’ authentication β†’ authorization β†’ input validation β†’ encryption β†’ logging. Break one layer, face the next.

The Airport Security System: To board a plane, you need ID (authentication), a valid ticket (authorization), baggage screening (input validation), and metal detectors (threat detection). Airport staff monitor cameras (logging) and respond to incidents. No single checkpoint is enough; you need all layers working together. That’s API security.

The Bank Vault: Banks don’t just lock the vault (encryption). They have armed guards (authentication), access control lists (authorization), cameras everywhere (logging), alarms on unusual activity (anomaly detection), and time-delayed locks (rate limiting). API security requires the same multi-layered approach.

The Submarine’s Hull: A submarine has an outer hull, inner hull, and compartmentalized sections. If one area floods, bulkhead doors seal it off (circuit breakers). API security uses similar defense-in-depth: if authentication fails, authorization catches it. If authorization fails, encryption limits damage.

Code Example

// Comprehensive API security implementation
import express from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import jwt from 'jsonwebtoken';
import { body, validationResult } from 'express-validator';
import crypto from 'crypto';

const app = express();

// 1. Security Headers (Helmet)
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

// 2. Rate Limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.',
  standardHeaders: true,
  legacyHeaders: false
});
app.use('/api/', limiter);

// 3. Authentication Middleware
function authenticate(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'Missing authentication token' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
}

// 4. Authorization Middleware (RBAC)
function authorize(...allowedRoles) {
  return (req, res, next) => {
    if (!req.user || !allowedRoles.includes(req.user.role)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: 'You do not have permission to access this resource'
      });
    }
    next();
  };
}

// 5. Object-Level Authorization (Prevent BOLA)
async function checkOwnership(req, res, next) {
  const resourceId = req.params.id;
  const resource = await db.findById(resourceId);

  if (!resource) {
    return res.status(404).json({ error: 'Resource not found' });
  }

  // Critical: Check ownership
  if (resource.userId !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({
      error: 'Forbidden',
      message: 'You can only access your own resources'
    });
  }

  req.resource = resource;
  next();
}

// 6. Input Validation & Sanitization
const validateUser = [
  body('email').isEmail().normalizeEmail(),
  body('username').isLength({ min: 3, max: 20 }).trim().escape(),
  body('age').optional().isInt({ min: 13, max: 120 })
];

// 7. Secure Endpoint Example
app.post('/api/users',
  authenticate,
  authorize('admin'),
  validateUser,
  async (req, res) => {
    // Check validation results
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    // Mass assignment protection: allowlist
    const allowedFields = ['email', 'username', 'age'];
    const userData = {};
    allowedFields.forEach(field => {
      if (req.body[field] !== undefined) {
        userData[field] = req.body[field];
      }
    });

    try {
      const user = await db.createUser(userData);

      // Don't expose sensitive fields
      const { password, ...safeUser } = user;

      res.status(201).json(safeUser);
    } catch (err) {
      // Don't leak internal errors
      res.status(500).json({ error: 'Internal server error' });
      logger.error('User creation failed', err);
    }
  }
);

// 8. Resource Access with BOLA Protection
app.get('/api/invoices/:id',
  authenticate,
  checkOwnership,
  async (req, res) => {
    // req.resource already validated by checkOwnership middleware
    res.json(req.resource);
  }
);

// 9. Audit Logging
function auditLog(req, res, next) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    userId: req.user?.id,
    method: req.method,
    path: req.path,
    ip: req.ip,
    userAgent: req.headers['user-agent']
  };

  // Log to secure storage (not console in production)
  logger.info('API Request', logEntry);
  next();
}

app.use(auditLog);

// 10. Error Handling (Don't leak info)
app.use((err, req, res, next) => {
  // Log full error internally
  logger.error('Unhandled error', err);

  // Send sanitized error to client
  res.status(err.status || 500).json({
    error: 'An error occurred',
    message: process.env.NODE_ENV === 'development' ? err.message : 'Please try again later'
  });
});

Diagram

graph TB
    subgraph Client["Client Application"]
        C[API Request]
    end

    subgraph Edge["Edge Protection"]
        WAF[Web Application Firewall
DDoS Protection] CDNCDN / [API Gateway
Geographic Filtering] end subgraph Gateway["API Gateway Layer"] RL[Rate Limiting
100 req/sec per IP] TLS[TLS Termination
Certificate Validation] end subgraph Auth["Authentication & Authorization"] A1Authentication
Verify JWT/[API Key] A2Authorization
Check [Permissions RBAC] A3[Object-Level Auth
Check Resource Ownership] end subgraph App["Application Layer"] VInput Validation
[Schema Validation] B[Business Logic
Rate Limits, Quotas] E[Encryption
Sensitive Data] end subgraph Data["Data Layer"] DB[(Database
Encrypted at Rest)] AuditLog[(Audit Logs
Immutable)] end subgraph Monitor["Monitoring & Response"] M[SIEM / Monitoring
Anomaly Detection] IR[Incident Response
Automatic Blocking] end C --> WAF WAF --> CDN CDN --> RL RL --> TLS TLS --> A1 A1 -->|Valid Token| A2 A1 -->|Invalid| X1[401 Unauthorized] A2 -->|Authorized| A3 A2 -->|Forbidden| X2[403 Forbidden] A3 -->|Owner Match| V A3 -->|BOLA Attempt| X3[403 BOLA Blocked] V -->|Valid Input| B V -->|Invalid Input| X4[400 Bad Request] B --> E E --> DB E --> AuditLog DB --> M AuditLog --> M M --> IR IR -.Auto-Block.-> WAF style WAF fill:#ffe6e6 style A1 fill:#e6f3ff style A2 fill:#e6f3ff style A3 fill:#e6f3ff style V fill:#fffbe6 style E fill:#e6ffe6 style M fill:#f3e6ff style X1 fill:#ffcccc style X2 fill:#ffcccc style X3 fill:#ffcccc style X4 fill:#ffcccc

Security Notes

SECURITY NOTES
CRITICAL - API security requires defense-in-depth with multiple layers. Always use HTTPS/TLS for all API traffic; never send sensitive data over HTTP. Implement authentication on every non-public endpoint using industry-standard protocols (OAuth 2.0, JWT, API keys). Validate tokens on every request; never trust expired or unverified tokens. Implement fine-grained authorization checking both role-based (RBAC) and object-level (BOLA protection) permissions. Validate and sanitize ALL input using allowlists, not blocklists. Use parameterized queries or ORMs to prevent SQL injection. Implement rate limiting at multiple levels: global, per-user, per-IP, per-endpoint. Log all security events (failed auth, permission denials, unusual patterns) to immutable storage. Monitor logs for attack patterns using SIEM tools. Implement automated incident response for known attack signatures. Never expose internal error details or stack traces to clients. Use security headers (Helmet.js) to prevent XSS, clickjacking, and other browser-based attacks. Rotate secrets regularly (API keys, JWT secrets, database credentials). Implement API versioning to allow security patches without breaking clients. Conduct regular security audits and penetration testing. Follow OWASP API Security Top 10 guidelines. Implement least privilege principle: give minimum permissions required. Use encrypted storage for sensitive data at rest. Implement CORS policies carefully to prevent unauthorized origins. Never trust client-side validation; always validate server-side. Use security scanners in CI/CD pipelines to catch vulnerabilities before deployment.

Best Practices

  1. OWASP API Security Top 10: Address all 10 categories: Broken Object Level Authorization, Broken User Authentication, Excessive Data Exposure, Lack of Resources & Rate Limiting, Broken Function Level Authorization, Mass Assignment, Security Misconfiguration, Injection, Improper Assets Management, Insufficient Logging & Monitoring.

  2. Defense in Depth: Layer security controls (WAF β†’ rate limiting β†’ authentication β†’ authorization β†’ input validation β†’ encryption β†’ logging). If one fails, others catch the attack.

  3. Zero Trust Architecture: Never trust, always verify. Authenticate and authorize every request, even from internal services. Don’t assume network location implies trust.

  4. Principle of Least Privilege: Give users/services the minimum permissions needed. Default deny, explicitly allow. Regularly audit permissions.

  5. Secure by Default: New endpoints should require authentication by default. Opt-in to public access, not opt-out of security.

  6. Security Testing in CI/CD: Run automated security scans (SAST, DAST, dependency scanning) on every commit. Block deployments that fail security checks.

  7. API Gateway Pattern: Centralize security controls (auth, rate limiting, logging) in an API gateway. Don’t duplicate security logic across microservices.

  8. Regular Security Audits: Conduct penetration testing quarterly. Review OWASP Top 10 compliance annually. Monitor CVE databases for dependency vulnerabilities.

Common Mistakes

Authentication Without Authorization: Verifying identity but not checking permissions. User A can access User B’s data because the API only checks if the request is authenticated, not authorized.

Client-Side Security: Trusting client input or client-side validation. Attackers control the client and can bypass all client-side checks.

Hardcoded Secrets: Committing API keys, JWT secrets, or database passwords to version control. Use environment variables and secret management services.

No Rate Limiting: Allowing unlimited requests enables DDoS attacks, brute force, and scraping. Always implement rate limits.

Excessive Data Exposure: Returning full database objects when clients only need specific fields. Use DTOs (Data Transfer Objects) and field-level authorization.

Mass Assignment Vulnerabilities: Accepting all JSON fields without validation. Attackers add "role": "admin" to privilege escalation.

Ignoring HTTP Methods: Treating GET the same as POST. GET should never mutate state; enforce idempotency rules.

No Logging: Without audit logs, you can’t detect breaches or investigate incidents. Log authentication, authorization failures, and sensitive operations.

Trusting Internal Networks: Assuming internal APIs don’t need security because they’re “behind the firewall.” Modern networks are flat; assume breach.

Deprecated Endpoint Management: Leaving old API versions running indefinitely creates security debt. Implement API lifecycle management with sunset timelines.

Standards & RFCs