APIs fail security review in predictable ways, and the most damaging failure is also the most common: authenticating the caller correctly but never checking whether that particular caller is entitled to that particular record.
Authorisation is per-object, not per-endpoint
An endpoint that returns a record by identifier must verify that the authenticated caller owns or may access that specific record. Where this check is missing, changing an identifier in the URL returns somebody else's data. This single class of bug accounts for a large share of real API breaches, and it is invisible to anyone testing only with their own account.
Do not rely on unguessable identifiers
Sequential integers make enumeration trivial, and random identifiers only make it slower. Identifiers leak through logs, referrer headers, shared links, and support tickets. Use random identifiers to reduce casual enumeration, and treat the ownership check as the actual control.
Validate input strictly
- Define a schema per endpoint and reject anything that does not match.
- Never bind request bodies directly to database models — mass assignment allows callers to set fields you did not intend to expose.
- Enforce maximum sizes on bodies, arrays, and strings.
- Validate types and ranges rather than coercing quietly.
Rate limit meaningfully
Limit per authenticated account as well as per IP, since a single account behind many addresses is a common abuse pattern. Apply stricter limits to authentication, password reset, and anything that sends mail or calls a paid service. Return a clear status and retry guidance rather than failing opaquely.
Return less
Serialise explicitly rather than returning whole database records. Internal fields, password hashes, and related-object data leak through generic serialisers with depressing regularity. Define the response shape deliberately, and never include internal error detail or stack traces in a response.
Credentials and transport
HTTPS everywhere, tokens with short lifetimes and a refresh mechanism, and scoped API keys that can be rotated per integration. Never accept credentials in query strings, which end up in server logs and browser history. Log authentication events and authorisation failures — repeated failures are one of the clearest early signals of an attack in progress.