Skip to main content

Security

Security in microservices is more complex than monoliths due to increased attack surface. Every service-to-service call needs authentication and authorization.
Learning Objectives:
  • Implement JWT-based authentication
  • Design authorization strategies
  • Set up mutual TLS (mTLS)
  • Manage secrets securely
  • Protect against common attacks

Security Challenges in Microservices

┌─────────────────────────────────────────────────────────────────────────────┐
│                    MICROSERVICES SECURITY CHALLENGES                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ATTACK SURFACE                                                              │
│  ──────────────                                                              │
│                                                                              │
│  Monolith:                      Microservices:                              │
│  ┌────────────┐                 ┌─────┐  ┌─────┐  ┌─────┐                   │
│  │            │                 │     │──│     │──│     │                   │
│  │   Single   │   vs            │  A  │  │  B  │  │  C  │                   │
│  │   Entry    │                 │     │──│     │──│     │                   │
│  │            │                 └─────┘  └─────┘  └─────┘                   │
│  └────────────┘                    │        │        │                      │
│       │                            └────────┴────────┘                      │
│  1 entry point                   N entry points + M connections             │
│                                                                              │
│                                                                              │
│  CHALLENGES:                                                                 │
│  ────────────                                                                │
│                                                                              │
│  • How do services authenticate each other?                                 │
│  • How to propagate user identity across services?                          │
│  • Where to enforce authorization?                                          │
│  • How to secure service-to-service communication?                          │
│  • How to manage secrets across many services?                              │
│  • How to audit access across distributed system?                           │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Authentication Strategies

JWT-Based Authentication

┌─────────────────────────────────────────────────────────────────────────────┐
│                    JWT AUTHENTICATION FLOW                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  1. User Login                                                               │
│  ┌──────────┐         ┌──────────────┐                                      │
│  │  Client  │──login─▶│  Auth Service │                                      │
│  │          │◀─JWT────│  (issues JWT) │                                      │
│  └──────────┘         └──────────────┘                                      │
│                                                                              │
│  2. Access Services                                                          │
│  ┌──────────┐  Authorization: Bearer <JWT>   ┌──────────────┐               │
│  │  Client  │───────────────────────────────▶│  API Gateway │               │
│  └──────────┘                                └──────┬───────┘               │
│                                                      │                      │
│  3. JWT Propagation                                  │ JWT                  │
│                                                      ▼                      │
│                       ┌──────────┐      ┌──────────┐      ┌──────────┐     │
│                       │  Order   │─JWT─▶│ Payment  │─JWT─▶│ Inventory│     │
│                       │ Service  │      │ Service  │      │ Service  │     │
│                       └──────────┘      └──────────┘      └──────────┘     │
│                                                                              │
│  JWT Structure:                                                              │
│  ─────────────                                                               │
│  Header.Payload.Signature                                                    │
│                                                                              │
│  Payload: {                                                                  │
│    "sub": "user123",                                                        │
│    "email": "[email protected]",                                             │
│    "roles": ["customer", "premium"],                                        │
│    "permissions": ["orders:read", "orders:write"],                          │
│    "exp": 1705347600,                                                       │
│    "iss": "auth-service"                                                    │
│  }                                                                           │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

JWT Service Implementation

// services/AuthService.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

class AuthService {
  constructor(options = {}) {
    this.accessTokenSecret = process.env.JWT_ACCESS_SECRET;
    this.refreshTokenSecret = process.env.JWT_REFRESH_SECRET;
    this.accessTokenExpiry = options.accessTokenExpiry || '15m';
    this.refreshTokenExpiry = options.refreshTokenExpiry || '7d';
    this.issuer = options.issuer || 'auth-service';
  }

  async login(email, password) {
    const user = await this.userRepository.findByEmail(email);
    
    if (!user || !await bcrypt.compare(password, user.passwordHash)) {
      throw new UnauthorizedError('Invalid credentials');
    }

    const tokens = this.generateTokens(user);
    
    // Store refresh token
    await this.tokenRepository.storeRefreshToken(
      user.id,
      tokens.refreshToken,
      this.refreshTokenExpiry
    );

    return tokens;
  }

  generateTokens(user) {
    const payload = {
      sub: user.id,
      email: user.email,
      roles: user.roles,
      permissions: this.getPermissionsForRoles(user.roles)
    };

    const accessToken = jwt.sign(payload, this.accessTokenSecret, {
      expiresIn: this.accessTokenExpiry,
      issuer: this.issuer,
      audience: 'microservices'
    });

    const refreshToken = jwt.sign(
      { sub: user.id, type: 'refresh' },
      this.refreshTokenSecret,
      {
        expiresIn: this.refreshTokenExpiry,
        issuer: this.issuer
      }
    );

    return { accessToken, refreshToken };
  }

  async refreshAccessToken(refreshToken) {
    try {
      const decoded = jwt.verify(refreshToken, this.refreshTokenSecret);
      
      // Verify token exists in store (not revoked)
      const storedToken = await this.tokenRepository.getRefreshToken(decoded.sub);
      if (storedToken !== refreshToken) {
        throw new UnauthorizedError('Invalid refresh token');
      }

      const user = await this.userRepository.findById(decoded.sub);
      if (!user) {
        throw new UnauthorizedError('User not found');
      }

      return this.generateTokens(user);
    } catch (error) {
      throw new UnauthorizedError('Invalid refresh token');
    }
  }

  async logout(userId, refreshToken) {
    // Revoke refresh token
    await this.tokenRepository.revokeRefreshToken(userId);
    
    // Optionally add access token to blacklist
    // await this.tokenRepository.blacklistAccessToken(accessToken);
  }

  getPermissionsForRoles(roles) {
    const rolePermissions = {
      admin: ['*'],
      customer: [
        'orders:read',
        'orders:create',
        'profile:read',
        'profile:update'
      ],
      support: [
        'orders:read',
        'users:read',
        'tickets:*'
      ]
    };

    const permissions = new Set();
    roles.forEach(role => {
      const perms = rolePermissions[role] || [];
      perms.forEach(p => permissions.add(p));
    });

    return Array.from(permissions);
  }
}

module.exports = { AuthService };

JWT Middleware

// middleware/auth.js
const jwt = require('jsonwebtoken');

function authenticate(options = {}) {
  const { secret = process.env.JWT_ACCESS_SECRET, optional = false } = options;

  return async (req, res, next) => {
    const authHeader = req.headers.authorization;
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      if (optional) {
        return next();
      }
      return res.status(401).json({ error: 'Missing authorization header' });
    }

    const token = authHeader.substring(7);

    try {
      const decoded = jwt.verify(token, secret, {
        issuer: 'auth-service',
        audience: 'microservices'
      });

      // Check token blacklist
      const isBlacklisted = await tokenBlacklist.check(token);
      if (isBlacklisted) {
        return res.status(401).json({ error: 'Token revoked' });
      }

      req.user = {
        id: decoded.sub,
        email: decoded.email,
        roles: decoded.roles,
        permissions: decoded.permissions
      };

      next();
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        return res.status(401).json({ error: 'Token expired' });
      }
      return res.status(401).json({ error: 'Invalid token' });
    }
  };
}

// Usage
app.use('/api', authenticate());

Authorization Patterns

Role-Based Access Control (RBAC)

// middleware/authorize.js
function authorize(...allowedRoles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    const hasRole = req.user.roles.some(role => allowedRoles.includes(role));
    
    if (!hasRole) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
}

// Usage
app.get('/admin/users', authenticate(), authorize('admin'), (req, res) => {
  // Only admins can access
});

app.get('/orders', authenticate(), authorize('customer', 'support', 'admin'), (req, res) => {
  // Customers, support, and admins can access
});

Permission-Based Authorization

// middleware/permission.js
function checkPermission(...requiredPermissions) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    const userPermissions = req.user.permissions;
    
    // Check for wildcard permission
    if (userPermissions.includes('*')) {
      return next();
    }

    const hasPermission = requiredPermissions.every(required => {
      // Check exact match
      if (userPermissions.includes(required)) {
        return true;
      }
      
      // Check wildcard match (e.g., 'orders:*' matches 'orders:read')
      const [resource, action] = required.split(':');
      if (userPermissions.includes(`${resource}:*`)) {
        return true;
      }
      
      return false;
    });

    if (!hasPermission) {
      return res.status(403).json({
        error: 'Insufficient permissions',
        required: requiredPermissions,
        have: userPermissions
      });
    }

    next();
  };
}

// Usage
app.post('/orders', 
  authenticate(),
  checkPermission('orders:create'),
  createOrder
);

app.delete('/orders/:id',
  authenticate(),
  checkPermission('orders:delete'),
  deleteOrder
);

Attribute-Based Access Control (ABAC)

// authorization/PolicyEngine.js
class PolicyEngine {
  constructor() {
    this.policies = [];
  }

  addPolicy(policy) {
    this.policies.push(policy);
  }

  evaluate(context) {
    for (const policy of this.policies) {
      const result = policy.evaluate(context);
      if (result.effect === 'DENY') {
        return { allowed: false, reason: result.reason };
      }
      if (result.effect === 'ALLOW') {
        return { allowed: true };
      }
    }
    return { allowed: false, reason: 'No matching policy' };
  }
}

// Policy definitions
const policies = [
  {
    name: 'owner-access',
    evaluate: (context) => {
      const { user, resource, action } = context;
      
      if (resource.ownerId === user.id) {
        return { effect: 'ALLOW' };
      }
      return { effect: 'CONTINUE' };
    }
  },
  {
    name: 'admin-access',
    evaluate: (context) => {
      const { user } = context;
      
      if (user.roles.includes('admin')) {
        return { effect: 'ALLOW' };
      }
      return { effect: 'CONTINUE' };
    }
  },
  {
    name: 'time-based-access',
    evaluate: (context) => {
      const { user, action } = context;
      const hour = new Date().getHours();
      
      // No financial operations outside business hours for non-admins
      if (action.startsWith('payment:') && !user.roles.includes('admin')) {
        if (hour < 9 || hour > 17) {
          return {
            effect: 'DENY',
            reason: 'Financial operations only available during business hours'
          };
        }
      }
      return { effect: 'CONTINUE' };
    }
  }
];

// Middleware using policy engine
function abacAuthorize(action, getResource) {
  return async (req, res, next) => {
    const resource = await getResource(req);
    
    const context = {
      user: req.user,
      resource,
      action,
      environment: {
        ip: req.ip,
        time: new Date(),
        method: req.method
      }
    };

    const result = policyEngine.evaluate(context);
    
    if (!result.allowed) {
      return res.status(403).json({
        error: 'Access denied',
        reason: result.reason
      });
    }

    req.resource = resource;
    next();
  };
}

// Usage
app.put('/orders/:id',
  authenticate(),
  abacAuthorize('orders:update', async (req) => {
    return orderRepository.findById(req.params.id);
  }),
  updateOrder
);

Service-to-Service Authentication

API Keys for Internal Services

// middleware/serviceAuth.js
const crypto = require('crypto');

class ServiceAuthenticator {
  constructor(options = {}) {
    this.services = new Map();
    this.algorithm = 'sha256';
  }

  registerService(serviceId, secret) {
    this.services.set(serviceId, secret);
  }

  generateSignature(serviceId, timestamp, body) {
    const secret = this.services.get(serviceId);
    if (!secret) {
      throw new Error(`Unknown service: ${serviceId}`);
    }

    const payload = `${serviceId}:${timestamp}:${JSON.stringify(body)}`;
    return crypto
      .createHmac(this.algorithm, secret)
      .update(payload)
      .digest('hex');
  }

  verify(serviceId, timestamp, body, signature) {
    // Check timestamp freshness (prevent replay attacks)
    const age = Date.now() - parseInt(timestamp);
    if (age > 300000) { // 5 minutes
      return { valid: false, reason: 'Request expired' };
    }

    const expectedSignature = this.generateSignature(serviceId, timestamp, body);
    const valid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );

    return { valid, reason: valid ? null : 'Invalid signature' };
  }
}

// Middleware
function serviceAuthenticate(authenticator) {
  return (req, res, next) => {
    const serviceId = req.headers['x-service-id'];
    const timestamp = req.headers['x-timestamp'];
    const signature = req.headers['x-signature'];

    if (!serviceId || !timestamp || !signature) {
      return res.status(401).json({ error: 'Missing service authentication' });
    }

    const result = authenticator.verify(serviceId, timestamp, req.body, signature);
    
    if (!result.valid) {
      return res.status(401).json({ error: result.reason });
    }

    req.service = { id: serviceId };
    next();
  };
}

// Client-side usage
class ServiceClient {
  constructor(serviceId, secret) {
    this.serviceId = serviceId;
    this.authenticator = new ServiceAuthenticator();
    this.authenticator.registerService(serviceId, secret);
  }

  async request(url, options = {}) {
    const timestamp = Date.now().toString();
    const body = options.body || {};
    const signature = this.authenticator.generateSignature(
      this.serviceId,
      timestamp,
      body
    );

    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'X-Service-ID': this.serviceId,
        'X-Timestamp': timestamp,
        'X-Signature': signature,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    });
  }
}

Mutual TLS (mTLS)

┌─────────────────────────────────────────────────────────────────────────────┐
│                         MUTUAL TLS (mTLS)                                    │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Regular TLS:                    Mutual TLS:                                │
│  ────────────                    ──────────────                             │
│                                                                              │
│  Client                          Client                                      │
│    │                               │                                        │
│    │──▶ Verify Server Cert         │──▶ Verify Server Cert                  │
│    │                               │                                        │
│    │    (server authenticated)     │    (server authenticated)              │
│    │                               │                                        │
│    │                               │◀── Verify Client Cert                  │
│    │                               │                                        │
│    ▼                               ▼    (BOTH authenticated)                │
│  Server                          Server                                      │
│                                                                              │
│                                                                              │
│  Certificate Chain:                                                          │
│  ─────────────────                                                          │
│                                                                              │
│       ┌─────────────────────┐                                               │
│       │     Root CA         │                                               │
│       │  (Trusted by all)   │                                               │
│       └─────────┬───────────┘                                               │
│                 │                                                           │
│       ┌─────────┴───────────┐                                               │
│       │   Intermediate CA   │                                               │
│       └─────────┬───────────┘                                               │
│                 │                                                           │
│    ┌────────────┼────────────┐                                              │
│    │            │            │                                              │
│    ▼            ▼            ▼                                              │
│  ┌─────┐    ┌─────┐    ┌─────┐                                             │
│  │Order│    │Pay  │    │Inv  │                                             │
│  │Cert │    │Cert │    │Cert │                                             │
│  └─────┘    └─────┘    └─────┘                                             │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

mTLS Configuration

// security/mtls.js
const https = require('https');
const fs = require('fs');
const tls = require('tls');

class MTLSServer {
  constructor(options) {
    this.caCert = fs.readFileSync(options.caPath);
    this.serverCert = fs.readFileSync(options.certPath);
    this.serverKey = fs.readFileSync(options.keyPath);
  }

  createServer(app) {
    return https.createServer({
      ca: this.caCert,
      cert: this.serverCert,
      key: this.serverKey,
      requestCert: true,        // Request client certificate
      rejectUnauthorized: true  // Reject invalid certificates
    }, app);
  }

  // Middleware to extract client certificate info
  extractClientCert() {
    return (req, res, next) => {
      const cert = req.socket.getPeerCertificate();
      
      if (!cert || Object.keys(cert).length === 0) {
        return res.status(401).json({ error: 'Client certificate required' });
      }

      req.clientCert = {
        subject: cert.subject,
        issuer: cert.issuer,
        valid_from: cert.valid_from,
        valid_to: cert.valid_to,
        fingerprint: cert.fingerprint,
        serialNumber: cert.serialNumber
      };

      // Extract service name from certificate CN
      req.serviceName = cert.subject.CN;

      next();
    };
  }
}

class MTLSClient {
  constructor(options) {
    this.agent = new https.Agent({
      ca: fs.readFileSync(options.caPath),
      cert: fs.readFileSync(options.certPath),
      key: fs.readFileSync(options.keyPath),
      rejectUnauthorized: true
    });
  }

  async request(url, options = {}) {
    return fetch(url, {
      ...options,
      agent: this.agent
    });
  }
}

// Usage
const mtlsServer = new MTLSServer({
  caPath: '/certs/ca.crt',
  certPath: '/certs/server.crt',
  keyPath: '/certs/server.key'
});

const server = mtlsServer.createServer(app);
app.use(mtlsServer.extractClientCert());

Certificate Generation Script

#!/bin/bash
# generate-certs.sh

# Create CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
  -subj "/CN=Microservices CA/O=MyCompany"

# Generate service certificate
generate_service_cert() {
  SERVICE=$1
  
  # Generate key
  openssl genrsa -out ${SERVICE}.key 2048
  
  # Generate CSR
  openssl req -new -key ${SERVICE}.key -out ${SERVICE}.csr \
    -subj "/CN=${SERVICE}/O=MyCompany"
  
  # Sign with CA
  openssl x509 -req -days 365 -in ${SERVICE}.csr \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out ${SERVICE}.crt
  
  echo "Generated certificates for ${SERVICE}"
}

generate_service_cert "order-service"
generate_service_cert "payment-service"
generate_service_cert "inventory-service"

Secrets Management

HashiCorp Vault Integration

// security/VaultClient.js
const vault = require('node-vault');

class VaultClient {
  constructor(options = {}) {
    this.client = vault({
      apiVersion: 'v1',
      endpoint: options.endpoint || process.env.VAULT_ADDR,
      token: options.token || process.env.VAULT_TOKEN
    });
    
    this.cache = new Map();
    this.leases = new Map();
  }

  async init() {
    // If using Kubernetes auth
    if (process.env.KUBERNETES_SERVICE_HOST) {
      await this.authenticateKubernetes();
    }
  }

  async authenticateKubernetes() {
    const jwt = require('fs').readFileSync(
      '/var/run/secrets/kubernetes.io/serviceaccount/token',
      'utf8'
    );

    const result = await this.client.kubernetesLogin({
      role: process.env.VAULT_ROLE,
      jwt
    });

    this.client.token = result.auth.client_token;
  }

  async getSecret(path, key = null) {
    // Check cache
    const cached = this.cache.get(path);
    if (cached && cached.expiry > Date.now()) {
      return key ? cached.data[key] : cached.data;
    }

    try {
      const result = await this.client.read(path);
      const data = result.data.data || result.data;
      
      // Cache for 5 minutes
      this.cache.set(path, {
        data,
        expiry: Date.now() + 300000
      });

      return key ? data[key] : data;
    } catch (error) {
      console.error(`Failed to read secret ${path}:`, error);
      throw error;
    }
  }

  async getDatabaseCredentials(role) {
    const result = await this.client.read(`database/creds/${role}`);
    
    // Store lease for renewal
    this.leases.set(role, {
      leaseId: result.lease_id,
      leaseDuration: result.lease_duration
    });

    // Schedule renewal
    this.scheduleRenewal(role, result.lease_duration);

    return {
      username: result.data.username,
      password: result.data.password
    };
  }

  scheduleRenewal(role, leaseDuration) {
    // Renew at 75% of lease duration
    const renewIn = leaseDuration * 0.75 * 1000;
    
    setTimeout(async () => {
      const lease = this.leases.get(role);
      if (lease) {
        try {
          const result = await this.client.write('sys/leases/renew', {
            lease_id: lease.leaseId
          });
          this.scheduleRenewal(role, result.lease_duration);
        } catch (error) {
          console.error(`Failed to renew lease for ${role}:`, error);
        }
      }
    }, renewIn);
  }
}

// Usage
const vaultClient = new VaultClient();
await vaultClient.init();

const dbCreds = await vaultClient.getDatabaseCredentials('order-service');
const apiKey = await vaultClient.getSecret('secret/api-keys', 'stripe');

Environment Variable Encryption

// security/secrets.js
const crypto = require('crypto');

class SecretManager {
  constructor(masterKey) {
    this.masterKey = Buffer.from(masterKey, 'hex');
  }

  encrypt(plaintext) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-gcm', this.masterKey, iv);
    
    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    const authTag = cipher.getAuthTag();
    
    return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
  }

  decrypt(encrypted) {
    const [ivHex, authTagHex, ciphertext] = encrypted.split(':');
    
    const iv = Buffer.from(ivHex, 'hex');
    const authTag = Buffer.from(authTagHex, 'hex');
    
    const decipher = crypto.createDecipheriv('aes-256-gcm', this.masterKey, iv);
    decipher.setAuthTag(authTag);
    
    let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
  }

  // Load and decrypt environment variables
  loadSecrets(encryptedEnv) {
    const secrets = {};
    
    for (const [key, value] of Object.entries(encryptedEnv)) {
      if (value.startsWith('ENC:')) {
        secrets[key] = this.decrypt(value.substring(4));
      } else {
        secrets[key] = value;
      }
    }
    
    return secrets;
  }
}

// Kubernetes Secret management
async function loadK8sSecret(secretName, namespace = 'default') {
  const k8s = require('@kubernetes/client-node');
  const kc = new k8s.KubeConfig();
  kc.loadFromCluster();
  
  const coreApi = kc.makeApiClient(k8s.CoreV1Api);
  
  const response = await coreApi.readNamespacedSecret(secretName, namespace);
  const secrets = {};
  
  for (const [key, value] of Object.entries(response.body.data)) {
    secrets[key] = Buffer.from(value, 'base64').toString('utf8');
  }
  
  return secrets;
}

Security Best Practices

Input Validation

// validation/sanitize.js
const Joi = require('joi');
const xss = require('xss');

// Schema validation
const orderSchema = Joi.object({
  customerId: Joi.string().uuid().required(),
  items: Joi.array().items(
    Joi.object({
      productId: Joi.string().uuid().required(),
      quantity: Joi.number().integer().min(1).max(100).required()
    })
  ).min(1).max(50).required(),
  shippingAddress: Joi.object({
    street: Joi.string().max(200).required(),
    city: Joi.string().max(100).required(),
    country: Joi.string().length(2).required()
  }).required()
});

function validateOrder(data) {
  const { error, value } = orderSchema.validate(data, {
    abortEarly: false,
    stripUnknown: true
  });

  if (error) {
    throw new ValidationError(error.details);
  }

  // Sanitize string fields
  value.shippingAddress.street = xss(value.shippingAddress.street);
  value.shippingAddress.city = xss(value.shippingAddress.city);

  return value;
}

// SQL Injection prevention - use parameterized queries
async function findOrder(orderId) {
  // BAD - SQL injection vulnerable
  // const result = await db.query(`SELECT * FROM orders WHERE id = '${orderId}'`);
  
  // GOOD - parameterized query
  const result = await db.query(
    'SELECT * FROM orders WHERE id = $1',
    [orderId]
  );
  
  return result.rows[0];
}

Rate Limiting

// middleware/rateLimit.js
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

const redis = new Redis(process.env.REDIS_URL);

// General API rate limit
const apiLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:api:'
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: {
    error: 'Too many requests',
    retryAfter: 'Please try again later'
  },
  standardHeaders: true,
  legacyHeaders: false
});

// Stricter limit for auth endpoints
const authLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:auth:'
  }),
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5, // 5 failed attempts
  skipSuccessfulRequests: true,
  message: {
    error: 'Too many login attempts',
    retryAfter: 'Account temporarily locked'
  }
});

// Apply
app.use('/api/', apiLimiter);
app.use('/auth/login', authLimiter);

Security Headers

// middleware/security.js
const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"]
    }
  },
  crossOriginEmbedderPolicy: true,
  crossOriginOpenerPolicy: true,
  crossOriginResourcePolicy: { policy: 'same-site' },
  dnsPrefetchControl: { allow: false },
  frameguard: { action: 'deny' },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  ieNoOpen: true,
  noSniff: true,
  originAgentCluster: true,
  permittedCrossDomainPolicies: { permittedPolicies: 'none' },
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
  xssFilter: true
}));

Interview Questions

Answer:Options:
  1. mTLS (Mutual TLS)
    • Both client and server present certificates
    • Strongest authentication
    • Used in service meshes (Istio, Linkerd)
  2. API Keys + HMAC
    • Service ID + timestamp + signature
    • Prevents replay attacks
    • Easier to implement than mTLS
  3. JWT Tokens
    • Service-specific JWTs
    • Can include permissions
    • Short expiry for security
Best Practice:
  • Use mTLS in production
  • API keys for development/simple cases
  • Always encrypt in transit (TLS)
Answer:Storage:
  • Never in localStorage (XSS vulnerable)
  • HttpOnly cookies (preferred)
  • Memory only for SPAs
Token Security:
  • Short expiry (15-30 minutes)
  • Refresh tokens for session management
  • Rotate secrets regularly
Validation:
  • Verify signature, issuer, audience
  • Check expiry and not-before claims
  • Validate against token blacklist
Algorithm:
  • Use RS256 or ES256 for production
  • Never use alg: none
  • Specify algorithm in verification
Answer:Never:
  • Hardcode in source code
  • Commit to version control
  • Store in plain environment variables
Best Practices:
  1. Secrets Manager (HashiCorp Vault, AWS Secrets Manager)
    • Dynamic secret generation
    • Automatic rotation
    • Audit logging
  2. Kubernetes Secrets
    • Encrypted at rest
    • RBAC for access control
    • Mount as files, not env vars
  3. Encryption
    • Encrypt secrets at rest
    • Use envelope encryption
    • Rotate encryption keys
  4. Principle of Least Privilege
    • Service-specific secrets
    • Time-limited access
    • Audit all access

Summary

Key Takeaways

  • JWT for user authentication
  • RBAC/ABAC for authorization
  • mTLS for service-to-service auth
  • Use secrets managers (Vault)
  • Defense in depth approach

Next Steps

In the next chapter, we’ll cover Containerization - Docker best practices for microservices.