APIs are the backbone of modern web applications, but they also represent a massive attack surface. Every endpoint you expose is a potential entry point for attackers. Building a secure API isn't about adding security as an afterthought — it needs to be woven into every design decision from the start.
Use short-lived JWTs for stateless authentication and pair them with refresh tokens stored inHttpOnly cookies. Never embed sensitive data in the JWT payload — it's base64-encoded, not encrypted. Implement token rotation so that if a refresh token is compromised, it can only be used once before invalidation. For server-to-server communication, use API keys with strict IP allowlisting and rotate them on a regular schedule.
Without rate limiting, your API is vulnerable to brute-force attacks, credential stuffing, and denial-of-service. Implement tiered rate limits: stricter limits on authentication endpoints (e.g., 5 requests per minute) and more relaxed limits on read-heavy routes. Use a sliding window algorithm backed by Redis for distributed environments. Always return a 429 Too Many Requests response with a Retry-After header so legitimate clients know when to retry.
Validate every piece of incoming data against a strict schema. Use libraries like Zod or Joi to define expected types, lengths, and patterns. Reject requests that don't conform rather than trying to sanitize them into shape. Pay special attention to nested objects and arrays — attackers often exploit deeply nested payloads to cause stack overflows or excessive memory consumption (known as "billion laughs"-style attacks).
Never expose stack traces, database errors, or internal paths in API responses. Use generic error messages for clients while logging detailed errors server-side. Differentiate between 4xx (client errors) and 5xx (server errors) consistently. A well-designed error response includes a machine-readable error code, a human-readable message, and a correlation ID for debugging — nothing more.
Layer your defenses: validate at the edge with a WAF, enforce authentication in middleware, check authorization at the route level, and validate input in the handler. Use CORS to restrict which origins can call your API. Enable HTTPS everywhere and set security headers likeStrict-Transport-Security and X-Content-Type-Options. Security is never a single layer — it's the sum of many small, deliberate choices.