Authentication answers who sent a request. Authorization decides whether that identity may perform this action on this resource. Production APIs fail when those two questions are treated as the same check.
Make authorization resource-aware
A route protected by a valid JWT is not automatically secure. For requests such as GET /orders/:id, the API must verify that the authenticated user owns the order or has an explicit role that grants access. Never trust a user ID, tenant ID, price, or role supplied by the client without checking it against server-side state.
const order = await Order.findById(req.params.id);
if (!order) return res.sendStatus(404);
if (!order.owner.equals(req.user.id) && req.user.role !== 'admin') {
return res.sendStatus(403);
}
Centralize policy, keep context
Reusable middleware is valuable, but a single isAuthenticated check cannot express every business rule. Separate authentication, coarse role checks, object ownership, tenant isolation, and state-dependent policies. Keep the final resource-level decision close enough to the query that it cannot be forgotten.
Validate shape and intent
Use allowlists for accepted properties. Reject unexpected fields rather than passing an entire request body into a database update. Validate types, ranges, lengths, formats, and state transitions on the server. Output filtering matters too: return only the fields the caller is permitted to see.
Control resource consumption
Apply rate limits to authentication, search, uploads, password recovery, expensive reports, and other abuse-sensitive flows. Also constrain payload size, pagination, query complexity, execution time, and concurrent work. A request can be syntactically valid and still be operationally abusive.
Treat integrations as untrusted
Validate responses from third-party APIs, use timeouts, limit redirects, and avoid passing remote data directly into templates, commands, or database queries. Store integration credentials outside source control and grant each credential only the permissions it needs.
Return less information
Consistent status codes help clients without exposing stack traces, database errors, internal paths, or whether a particular account exists. Log detailed diagnostic context on the server, attach a request ID, and return a restrained message to the caller.
Test abuse cases, not only success
- Can user A read, update, or delete user B's object?
- Can a regular account call an administrative function?
- Can the client modify protected fields?
- Can pagination, uploads, or filters exhaust resources?
- Does a revoked or expired session still work?
- Do logs capture denied actions without storing secrets?
Reference
The OWASP API Security Top 10 is a useful design and review baseline. Its authorization categories are especially relevant to APIs serving web and mobile clients.
