Skip to content

API Security Guidelines

Table of Contents

  1. Authentication
  2. Authorization
  3. Input Validation
  4. Data Protection
  5. Rate Limiting
  6. HTTPS & TLS
  7. CORS
  8. Security Headers
  9. API Keys
  10. Vulnerability Management
  11. Security Testing
  12. Incident Response

Authentication

OAuth 2.0 with OpenID Connect

  • Use OpenID Connect for authentication
  • Implement the Authorization Code flow for web applications
  • Use PKCE (Proof Key for Code Exchange) for public clients
  • Set appropriate token expiration times:
  • Access tokens: 15-60 minutes
  • Refresh tokens: 7-30 days
  • ID tokens: 5 minutes

JWT Best Practices

  • Use strong signing algorithms (RS256, ES256)
  • Set reasonable expiration times
  • Include standard claims (iss, sub, aud, exp, iat, nbf)
  • Validate all tokens on the server side
  • Store tokens securely (httpOnly, Secure, SameSite=Strict)

Example JWT Validation

const jwt = require('jsonwebtoken');

function validateToken(token) {
  return new Promise((resolve, reject) => {
    jwt.verify(
      token,
      process.env.PUBLIC_KEY,
      {
        algorithms: ['RS256'],
        issuer: 'https://auth.dexaminds.com',
        audience: 'api.dexaminds.com',
        clockTolerance: 30, // seconds
        maxAge: '1h',
      },
      (err, decoded) => {
        if (err) return reject(err);
        resolve(decoded);
      }
    );
  });
}

Authorization

Role-Based Access Control (RBAC)

  • Define clear roles and permissions
  • Implement attribute-based access control (ABAC) for fine-grained control
  • Use policy-based authorization for complex rules
  • Implement resource-level permissions

Example Authorization Middleware

function requirePermission(permission) {
  return (req, res, next) => {
    if (!req.user || !req.user.permissions.includes(permission)) {
      return res.status(403).json({
        error: {
          code: 'FORBIDDEN',
          message: 'Insufficient permissions',
          details: [{
            code: 'MISSING_PERMISSION',
            target: permission,
            message: `Requires '${permission}' permission`
          }]
        }
      });
    }
    next();
  };
}

Input Validation

Validation Rules

  • Validate all input data (headers, query params, request body, path params)
  • Use strong input validation libraries (Joi, class-validator, etc.)
  • Implement allow-listing (not block-listing)
  • Sanitize all output data
  • Set maximum lengths for all string inputs

Example Validation with Joi

const userSchema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string()
    .min(12)
    .max(72) // bcrypt max length
    .pattern(/[a-z]/)
    .pattern(/[A-Z]/)
    .pattern(/[0-9]/)
    .pattern(/[^a-zA-Z0-9]/)
    .required(),
  role: Joi.string().valid('user', 'admin', 'support').default('user'),
  metadata: Joi.object({
    termsAccepted: Joi.boolean().valid(true).required(),
    marketingOptIn: Joi.boolean().default(false)
  }).required()
});

Data Protection

Data Encryption

  • Encrypt sensitive data at rest (AES-256)
  • Use TLS 1.2+ for data in transit
  • Hash passwords with strong algorithms (Argon2, bcrypt, PBKDF2)
  • Encrypt sensitive fields in the database

Example Password Hashing

const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;

async function hashPassword(password) {
  return bcrypt.hash(password, SALT_ROUNDS);
}

async function verifyPassword(password, hash) {
  return bcrypt.compare(password, hash);
}

Rate Limiting

Implementation

  • Implement rate limiting by IP, user, and API key
  • Use sliding window or token bucket algorithm
  • Set appropriate limits based on endpoint sensitivity
  • Include rate limit headers in responses

Example Rate Limiting Headers

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 75
X-RateLimit-Reset: 1625040000
Retry-After: 60

HTTPS & TLS

Best Practices

  • Use TLS 1.2 or higher
  • Implement HSTS with preload
  • Disable weak ciphers and protocols
  • Use strong key exchange (ECDHE, DHE)
  • Rotate certificates regularly (90 days or less)
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
ECDHE-ECDSA-AES256-GCM-SHA384

CORS

Configuration

  • Be specific with allowed origins
  • Don't use wildcard (*) for credentials
  • Set appropriate headers
  • Preflight caching

Example CORS Headers

Access-Control-Allow-Origin: https://app.dexaminds.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400

Security Headers

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' cdn.example.com; img-src 'self' data:; style-src 'self' 'unsafe-inline';
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()

API Keys

Best Practices

  • Don't use API keys for user authentication
  • Rotate keys regularly
  • Set appropriate permissions and restrictions
  • Monitor and audit key usage
  • Implement key revocation

Example API Key Middleware

async function validateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  if (!apiKey) {
    return res.status(401).json({
      error: {
        code: 'UNAUTHORIZED',
        message: 'API key is required'
      }
    });
  }

  try {
    const keyInfo = await db.apiKeys.findByKey(apiKey);
    if (!keyInfo || keyInfo.revoked) {
      throw new Error('Invalid API key');
    }

    // Check rate limits
    if (await isRateLimited(keyInfo.id)) {
      return res.status(429).json({
        error: {
          code: 'RATE_LIMIT_EXCEEDED',
          message: 'Too many requests'
        }
      });
    }

    // Attach key info to request
    req.apiKey = keyInfo;
    next();
  } catch (error) {
    return res.status(401).json({
      error: {
        code: 'INVALID_API_KEY',
        message: 'Invalid or revoked API key'
      }
    });
  }
}

Vulnerability Management

Practices

  • Keep dependencies updated
  • Use dependency vulnerability scanning (npm audit, Snyk, Dependabot)
  • Perform regular security audits
  • Implement a bug bounty program
  • Follow responsible disclosure policies

Example .npmrc for Secure Installs

# Prevent executing arbitrary code during package install
ignore-scripts=true

# Always use HTTPS for registry
registry=https://registry.npmjs.org/

# Use strict SSL
strict-ssl=true

# Use package-lock.json
package-lock=true

Security Testing

Testing Types

  • Static Application Security Testing (SAST)
  • Dynamic Application Security Testing (DAST)
  • Software Composition Analysis (SCA)
  • Penetration testing
  • Security code reviews

OWASP Top 10 Coverage

  1. Injection: Use parameterized queries, ORMs
  2. Broken Authentication: Implement proper auth flows, MFA
  3. Sensitive Data Exposure: Encrypt data, use secure protocols
  4. XML External Entities (XXE): Disable XML external entities
  5. Broken Access Control: Implement proper authorization checks
  6. Security Misconfiguration: Harden configurations, disable debug modes
  7. Cross-Site Scripting (XSS): Output encoding, Content Security Policy
  8. Insecure Deserialization: Validate input, use safe serializers
  9. Using Components with Known Vulnerabilities: Regular updates, SCA tools
  10. Insufficient Logging & Monitoring: Comprehensive logging, alerting

Incident Response

Response Plan

  1. Preparation: Document procedures, assign roles
  2. Detection & Analysis: Monitor logs, detect anomalies
  3. Containment: Isolate affected systems
  4. Eradication: Remove threat, patch vulnerabilities
  5. Recovery: Restore services, verify integrity
  6. Post-Incident: Document lessons, update policies

Example Security Headers for APIs

# Prevent MIME type sniffing
X-Content-Type-Options: nosniff

# Prevent clickjacking
X-Frame-Options: DENY

# Enable XSS filter
X-XSS-Protection: 1; mode=block

# Content Security Policy
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:;

# Prevent iframe embedding
X-Permitted-Cross-Domain-Policies: none

# Disable caching for sensitive endpoints
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
Pragma: no-cache
Expires: 0

Secure Development Lifecycle (SDL)

Phases

  1. Training: Security awareness for developers
  2. Requirements: Define security requirements
  3. Design: Threat modeling, security architecture
  4. Implementation: Secure coding practices
  5. Verification: Code review, security testing
  6. Release: Security review, sign-off
  7. Response: Incident response plan

Security Checklist for Code Review

  • [ ] Input validation and output encoding
  • [ ] Authentication and session management
  • [ ] Authorization checks
  • [ ] Secure configuration
  • [ ] Cryptography usage
  • [ ] Error handling and logging
  • [ ] Data protection
  • [ ] Communication security
  • [ ] File operations
  • [ ] Memory management

Security Headers for Different Server Types

Nginx

add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' cdn.example.com; style-src 'self' 'unsafe-inline';";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";

Apache

Header set X-Content-Type-Options "nosniff"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' cdn.example.com; style-src 'self' 'unsafe-inline';"
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"

Express.js

const helmet = require('helmet');

app.use(helmet());
app.use(helmet.permittedCrossDomainPolicies());
app.use(helmet.referrerPolicy({ policy: 'same-origin' }));
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'"],
      imgSrc: ["'self'"],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  })
);

Secure API Design Principles

Principles

  1. Least Privilege: Grant minimum necessary permissions
  2. Defense in Depth: Multiple layers of security
  3. Fail Securely: Default deny, safe error messages
  4. Keep It Simple: Reduce attack surface
  5. Secure by Default: Secure configurations out of the box
  6. Separation of Duties: Divide responsibilities
  7. Audit Trails: Comprehensive logging
  8. Privacy by Design: Data protection from the start
  9. Keep Security Up-to-Date: Regular updates and patches
  10. Assume Breach: Plan for security incidents