// LOADING
// LOADING
// LOADING_ARTICLE
A REST API is the attack surface your application exposes to the world. Everything behind it—user data, business logic, infrastructure—is only as safe as the controls you place on each endpoint. This post walks through the layers that matter most.
Authentication answers who are you? Authorization answers what are you allowed to do?. Confusing the two is the source of many security bugs.
A common mistake is returning a 403 Forbidden from a route that leaks the resource's existence to an unauthorized caller. If the caller should not know the resource exists, return 404 Not Found instead.
Every field that arrives from the network is untrusted. Validate early, before any database query or business logic runs.
tsimport { z } from "zod"; const CreatePostSchema = z.object({ title: z.string().min(1).max(200), body: z.string().min(1).max(50_000), tags: z.array(z.string().max(50)).max(10).optional(), }); export async function POST(req: Request) { const body = await req.json(); const parsed = CreatePostSchema.safeParse(body); if (!parsed.success) { return Response.json( { error: "Invalid input", details: parsed.error.flatten() }, { status: 400 } ); } // safe to use parsed.data from here }
This pattern—using a library like Zod to parse before touching the data—prevents injection attacks, unexpected type coercions, and prototype pollution. Never call .toString() or spread incoming objects directly into database queries.
Rate limiting is both a DoS defense and a credential-stuffing countermeasure. Without it, a single client can overwhelm your server or iterate through millions of password guesses.
For detailed implementation patterns (token bucket, sliding window, per-user limits), see Rate Limiting Your Next.js APIs. The critical points:
429 Too Many Requests with a Retry-After header so legitimate clients can back off gracefully.Every component—database user, API key, IAM role—should have only the permissions required for its function.
DROP TABLE or CREATE INDEX rights in production.For a broader view of how least privilege fits into overall project structure, see How I Structure Large Next.js + Node.js Projects.
APIs leak data in subtle ways:
500 responses reveal implementation details. Log them server-side; return only a generic message to the client.crypto.timingSafeEqual) prevent attackers from inferring valid values via response latency.tsimport { timingSafeEqual, createHash } from "crypto"; function verifyToken(supplied: string, stored: string): boolean { const a = createHash("sha256").update(supplied).digest(); const b = createHash("sha256").update(stored).digest(); return a.length === b.length && timingSafeEqual(a, b); }
Strict-Transport-Security (HSTS).Content-Type: application/json explicitly on all JSON responses to prevent MIME-sniffing attacks.X-Content-Type-Options: nosniff and X-Frame-Options: DENY on API responses that may be consumed by browsers.Access-Control-Allow-Origin: * is rarely appropriate for authenticated APIs.For state-mutating operations, accepting the same request twice should not create duplicate effects. The pattern is to accept a client-supplied idempotency key, store it with the result, and return the cached response on replay. This also protects against replay attacks when combined with short key expiry. More on this pattern in Designing Idempotent APIs.
Strict-Transport-Security: max-age=63072000; includeSubDomainsX-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy — see Content Security Policy (CSP), ExplainedReferrer-Policy: strict-origin-when-cross-originSecurity controls only help if you know when they are being tested. Log authentication failures, authorization denials, validation errors, and rate-limit hits with enough context (IP, user agent, endpoint, timestamp) to reconstruct an attack. Set up alerts for anomalous volumes. For a fuller treatment of logging infrastructure, see Monitoring and Observability for Web Apps.
Securing a REST API is not a single step but a set of overlapping controls: authenticate every request, authorize every action, validate every byte of input, rate-limit every endpoint, and leak as little as possible in errors and responses. Each layer independently reduces risk; together they make attacks substantially more expensive. Review Common Web Security Threats for the broader threat landscape, and check out the projects page to see these patterns applied in real applications.