An API that works is not the same as an API that is safe to expose. The gap between the two is usually small: a key that never expires, an endpoint that trusts the user ID in the URL, an error page that prints a stack trace with your database hostname in it. None of that shows up in a demo. It shows up in logs at 3 a.m., or in a support ticket from a customer whose records someone else could read. Here is the pass worth running before you point public traffic at your service.
Start With Authentication You Can Rotate
Authentication answers "who is calling." If you cannot answer that with confidence, everything downstream is guesswork.
API keys are fine for narrow, server-to-server access, but treat them as identifiers rather than passwords: store a hash, show the raw value once at creation, and give every key an owner, a scope, and a revocation path. A key that cannot be turned off in thirty seconds is a liability.
Tokens, in practice
- Use short-lived access tokens with a refresh flow instead of tokens that live for a year.
- Validate signature, issuer, audience, and expiry on every request.
- Reject unsigned tokens and anything claiming an "alg: none" style bypass.
- Use vetted libraries for OAuth 2.0 and JWT rather than writing your own crypto.
- Never accept credentials in a query string. They end up in proxy logs, browser history, and analytics dashboards.
Enforce Authorization One Endpoint at a Time
Authentication without authorization means every logged-in user can read everything. The most common bug here is the insecure direct object reference: the request asks for /invoices/1042, the API fetches invoice 1042, and never checks whether the caller owns it. Change the number to 1043 and you are reading someone else's billing history.
Fix it by making the ownership check part of the query instead of an afterthought. If the database lookup itself is scoped to the authenticated subject, an attacker cannot ask for a row that is not theirs. Deny by default, give each endpoint an explicit scope, and test with a token that has none.
Then test again with a valid token belonging to a different tenant. Multi-tenant leaks hide in exactly that gap.
Put Limits on Everything a Caller Controls
Rate limiting keeps one misbehaving client from degrading the service for everyone else. Apply it at more than one level, and make it visible to clients.
- Per API key or user, per IP for unauthenticated traffic, and per endpoint where the cost differs.
- Stricter limits on login, password reset, and anything that sends email or SMS.
- A maximum page size, a maximum request body, and a maximum time your upstream calls may take.
Return 429 with a Retry-After header so clients can back off instead of hammering the endpoint. A sliding window or token bucket is usually enough; the exact numbers matter less than having any. For login endpoints, add exponential backoff and a lockout after repeated failures, and make sure the error message does not reveal whether the username exists.
Treat Every Field as Hostile Until Proven Otherwise
Input validation is where a lot of API security actually lives. Validate on the server, not in the client, and validate against what you expect rather than a list of things you want to block.
- Check type, length, range, and format for every field, including headers and query parameters.
- Use an allowlist of accepted fields so an extra "is_admin": true in a JSON body is ignored rather than assigned.
- Use parameterized queries or an ORM. Never build SQL by concatenating strings.
- For fields that accept a URL, block internal addresses and cloud metadata endpoints to prevent server-side request forgery.
- For uploads, inspect the content instead of trusting the Content-Type header, cap the size, and store files outside the web root.
When validation fails, return 400 with a message that helps the developer and nothing more. Do not echo raw input back into a response, a log line, or an admin page.
Get the Quiet Details Right
These items rarely make headlines, but they cause a lot of incidents.
- TLS everywhere, with modern protocol versions, and HSTS once you are certain every subdomain supports it.
- CORS pointed at specific origins. A wildcard origin combined with credentials is a mistake, not a shortcut.
- Debug modes and verbose errors off in production. Stack traces belong in your logs, not in responses.
- Secrets in a manager or environment variables, never in the repository. Rotate them on a schedule and immediately when someone leaves the team.
- Request logs that record method, path, status, and a request ID — with tokens, passwords, and personal data stripped before they hit disk.
- Alerts on spikes in 401 and 403 responses. A slow credential-stuffing attempt looks exactly like that pattern.
The Ten-Item Pre-Launch Pass
Before real traffic arrives, confirm each of these:
- Every endpoint requires authentication, except the few you have deliberately made public.
- Authorization is checked per object, not only per route.
- Tokens and keys expire, and you can revoke them.
- Rate limits and payload caps are active, returning proper 429 responses.
- Every input field is validated against an allowlist on the server.
- Database access goes through parameterized queries.
- Secrets live outside the codebase and rotate on a schedule.
- Errors are generic to callers and detailed in your logs.
- TLS, CORS, and security headers are configured for production.
- Logging and alerting will tell you about authentication failures worth knowing about.
Keep Checking After You Ship
A checklist is a snapshot, not a guarantee. New endpoints appear, dependencies get updated, and eventually someone pastes a key into a config file at the wrong moment. Add a short security review to your pull request template for anything touching auth, and rerun this list before each significant release. If you handle payments, health data, or personal information, have a professional review the setup and check what your jurisdiction requires — the technical work above is only part of that picture. Then ship, and keep an eye on the logs.
Photo: TheDigitalArtist / Pixabay


