← All articles
SecurityMay 27, 2025

Securing Next.js APIs: RBAC, Rate Limiting and Audit Logs

A practical hardening checklist for production APIs: role checks on every route, rate limits that bite, and audit trails you can actually query.

By Kibet Brian

API security fails in the boring places: a route that forgot its role check, a login form with no rate limit, an incident with no logs. This is the baseline I apply to every production API before it ships.

Enforce RBAC in middleware

Check roles at the route boundary, not deep in handlers where it is easy to forget. A single withRole wrapper keeps the policy visible and makes unaudited routes impossible to add by accident.

rbac.ts
export function withRole(roles: Role[], handler: Handler): Handler {
return async (req) => {
const session = await getSession(req);
if (!session || !roles.includes(session.role)) {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
return handler(req, session);
};
}

Rate limit what matters

Limit auth attempts, password resets, and expensive endpoints per IP and per account. Return 429 with a Retry-After header, and alert on sustained limit hits, that pattern is usually an attack or a broken client.

Log every sensitive action

  • •Record who did what, to which record, from which IP, and when.
  • •Make audit rows append-only, no updates or deletes through the app.
  • •Ship logs off the application database so incidents stay queryable.
  • •Review the trail on a schedule, logs nobody reads are decoration.

Authenticate every request

Prefer short-lived tokens and rotate signing secrets without downtime by accepting the previous secret during a grace window. Long-lived API keys belong in a vault with per-key scopes and an owner, never in chat logs or client bundles.

Scope data to the caller

Authentication says who is calling, authorization on the data says what they may touch. Filter every query by the caller's tenant and id, reject unexpected fields instead of silently ignoring them, and test with a second account trying to read the first account's records.

Security is a checklist you run on every route, not a feature you add at the end. Boring, consistent enforcement beats clever one-off protections every time.

Share this article