This guide is for developers and technical leads building or hardening a production API. By the end you will know the layers of defence an API actually needs, how to think about authentication and authorisation as separate concerns, the input validation that catches the most common attacks, the transport and logging hygiene that supports the rest of the defence, and the specific threats that get missed because the documentation focuses on the easier ones.
Who This Guide Is For
Developers, technical leads, and architects responsible for an API that serves real traffic — public, partner-facing, or internal but reachable from outside the trust boundary. The patterns apply whether you are building a small REST API for a single mobile app or a large API serving many integrations and clients. The threats change at scale; the foundations do not.
Before You Start
You should know the threat model in rough terms — who is on the other side of the API and what could they do that you do not want them to do. A partner API where you have a contract with every consumer has different threats to a public API that anyone can sign up for. An internal API behind a private network has different threats to one exposed to the internet.
You should also know what the API exposes — what data, what operations. A read-only API that returns public data is in a different security category to one that initiates payments or modifies sensitive records.
Authentication and Authorisation Are Different
The two words sound similar and they get conflated, but the security implications are different. Authentication answers “who is this?”. Authorisation answers “what are they allowed to do?”. An API needs both, separately, and the failure modes are different.
Common authentication patterns: API keys (simple, fine for server-to-server, fragile for user-facing apps), JWT bearer tokens (stateless, scaling well, careful with secret management), OAuth 2.0 (the right choice when third parties act on behalf of users), session cookies (the right choice for first-party browser-based UIs).
The choice depends on the context. A partner API typically uses API keys or OAuth client credentials. A user-facing mobile app typically uses OAuth or JWT. A browser-based first-party UI typically uses session cookies with CSRF protection. Mixing patterns within one API is fine but each pattern needs to be implemented correctly.
Authorisation is the layer that decides what each authenticated request can do. The two main approaches: role-based access control (RBAC — the user has a role, the role has permissions, the request checks the permission) and attribute-based access control (ABAC — the request is allowed based on properties of the user, the resource, and the context). Most APIs need RBAC at minimum; ABAC adds finer-grained control when needed.
The mistake to avoid is enforcing authorisation only in the UI. If the UI hides a button that calls the admin endpoint, but the endpoint itself does not check permissions, anyone with a valid token can call the endpoint directly. Every endpoint must check authorisation, regardless of what the UI shows.
Validate Every Input
Input validation is the single most effective defence against the most common attacks. SQL injection, cross-site scripting, command injection, path traversal, denial-of-service via malformed input — almost all of them depend on input that the application accepted when it should have rejected.
The pattern that works: define a schema for every endpoint’s input. Validate against the schema before any business logic runs. Reject anything that does not match.
The schema covers: expected fields (no extras), types (strings stay strings), formats (emails match an email pattern, dates parse correctly, IDs match the expected shape), lengths (a name is not 50,000 characters), ranges (an age is between 0 and 150), allowed values (a status is one of a defined set).
Validation is not the same as sanitisation. Validation rejects bad input; sanitisation tries to clean it. Sanitisation is dangerous because it requires you to anticipate every form of bad input. Validation is safer because anything you did not explicitly allow is rejected by default.
For SQL specifically: use parameterised queries, always. Never concatenate user input into a SQL string, ever, anywhere, no matter how carefully you “escape” it. The class of SQL injection bugs only disappears when there is no string-concatenated SQL anywhere in the codebase.
A concrete example. An API endpoint that accepts a search query was returning results based on the input. The query was passed to a Postgres full-text search. The team thought string concatenation was safe because they were “only doing text search”. A penetration tester found that a particular malformed input triggered a Postgres error revealing internal data. The fix was to use the parameterised query API. The lesson generalises: do not trust input, do not concatenate, validate against an explicit schema, parameterise every query.
Use TLS Everywhere
Every API endpoint should be HTTPS only. HTTP should not be enabled at all on production; if a client connects via HTTP, redirect them or reject them.
The reasons are obvious but worth restating: HTTP transmits credentials, tokens, and payloads in plaintext. Anyone on the network path can read them — coffee shop wifi, compromised routers, hostile ISPs. TLS prevents this. The performance cost is negligible; the security improvement is total.
Beyond enabling TLS, the configuration matters. Use modern cipher suites (TLS 1.2 minimum, TLS 1.3 preferred). Disable old SSL versions entirely. Use HSTS to tell clients not to fall back to HTTP. Use OCSP stapling or short-lived certificates for revocation. The default configuration of most modern web servers is reasonable; the mistake is to leave it at “the defaults from 2015”.
For APIs called by mobile apps, certificate pinning adds another layer — the client verifies the specific certificate, not just that some valid certificate is presented. This defends against attacks where a malicious certificate has been installed on the device. It also makes certificate rotation more careful; pinning to a public key rather than a specific certificate is usually the right balance.
Rate Limit, Always
Every API endpoint needs rate limiting, even if you trust every client. The reasons:
- A buggy client can spike traffic accidentally. Without limits, your infrastructure absorbs the burst.
- Credential stuffing and password spraying depend on volume. A login endpoint with no limits is a sitting target.
- DDoS amplification attacks abuse APIs that return more data than the request contained. Limits prevent your API from being weaponised.
Rate limiting deserves its own treatment; How to Rate-Limit an API covers the algorithms, scopes, and policies in detail. The minimum for any production API: per-IP limits on unauthenticated endpoints, per-API-key limits on authenticated endpoints, tighter limits on expensive or sensitive endpoints, and 429 responses with the right headers when limits are hit.
Log What Will Help You Investigate
Security logging is what makes incident investigation possible. Without logs, an after-the-fact question like “did anyone access this resource yesterday?” has no answer.
The events to log: authentication attempts (successful and failed), authorisation failures (a user tried to access something they were not allowed to), input validation failures (often the first sign of probing), rate limit hits, and sensitive operations (data exports, permission changes, account creation).
Each log entry needs enough context: timestamp, source IP, authenticated user (if any), endpoint, request ID, outcome. Without the request ID, correlating across systems is impossible. Without the user, attributing actions is impossible.
Do not log what you do not need. Logging passwords, API keys, full credit card numbers, or other secrets is a data exposure waiting to happen. Logging should be selective by design — capture what helps investigation, exclude what creates risk.
Centralise logs somewhere queryable. Logs scattered across servers and tools are not useful when something happens. A log aggregator (Datadog, Loki, Elastic, CloudWatch) gives you one place to search.
Handle Errors Without Leaking Information
Error messages from a production API should be helpful enough for legitimate clients to fix their requests, but not so detailed that they reveal information useful to an attacker.
What to avoid: stack traces in production responses, database error messages that reveal schema, internal IP addresses, file paths, version numbers of frameworks (these get used to look up known vulnerabilities), and specific failure reasons that aid enumeration (“user not found” vs “wrong password” lets an attacker confirm which usernames exist).
What to do instead: generic error responses with an error code, a correlation ID, and a short message. The detailed error goes to the server logs, tagged with the correlation ID, so when a client reports the error you can look up the full context. The client knows enough to act; an attacker does not learn the internals.
A typical pattern. The API returns {"error": "invalid_request", "correlation_id": "abc-123"} to the client and logs the full stack trace and context internally against that correlation ID. Support can trace the issue; an attacker has nothing to work with.
Protect the Forgotten Endpoints
Some endpoints get more security attention than others. The login endpoint is hardened to within an inch of its life. The data export endpoint, the admin panel, and the webhook endpoint are sometimes left soft.
The forgotten endpoints to harden:
- Password reset: rate-limited per email, with anti-enumeration response (return the same message whether the email exists or not), with tokens that are random, single-use, and short-lived.
- Webhook receivers: signature verification, idempotency, fail-closed on invalid input (How to Handle Webhooks Reliably covers this in detail).
- File uploads: type checking, size limits, virus scanning where the upload is shared with other users, storage isolation so an uploaded file cannot execute on the server.
- Admin endpoints: stricter rate limits, additional authentication factor where possible, full audit logging.
- Bulk operations: queries that can return large amounts of data, exports, search. These can be abused for data exfiltration if not bounded.
A security review should look at every endpoint, not just the headline ones. The endpoints that handle 1% of traffic but 90% of sensitive operations are often the ones that get the least scrutiny.
Common Mistakes
- UI-only authorisation. The button is hidden, but the endpoint still works for anyone with a valid token. Every endpoint checks authorisation, regardless of the UI.
- Sanitising instead of validating. Trying to clean bad input fails eventually. Validating against an explicit allowed shape is robust.
- String-concatenated SQL. No matter the framework or language, do not concatenate user input into queries. Parameterise everything.
- HTTP allowed alongside HTTPS. TLS is not optional. Redirect or reject HTTP entirely.
- No rate limiting on the login endpoint. Credential stuffing is one of the most common attacks. The defence is rate limiting, plus alerting on unusual failed-login spikes.
- Error responses with stack traces. The class of information that should never reach the client. Generic message, correlation ID, full detail in the server logs.
- Logs that include secrets. Passwords, tokens, full card numbers. Once in the log aggregator, they live on. Be selective.
- Forgotten endpoints. Password reset, file upload, webhook receiver. These often get less attention than the headline endpoints and are exactly where the threats land.
What Good Looks Like
A well-secured API uses TLS everywhere with modern configuration, authenticates every request with a robust pattern matched to the consumer (API key, OAuth, JWT, or session cookie), authorises every request independently of UI, validates input against explicit schemas and parameterises every database query, rate limits aggressively, logs security-relevant events with enough context to investigate and excludes secrets from the logs, returns generic error responses with correlation IDs while logging full detail server-side, and treats every endpoint — including the forgotten ones — to the same security review. The result is a system that can be probed without yielding signal, attacked without yielding damage, and investigated when something looks unusual.
Next Steps
If your API is multi-tenant, How to Design a Multi-Tenant Database covers the data layer that the API security depends on. If the API receives webhooks or sends them, How to Handle Webhooks Reliably covers that adjacent topic. If the broader operational security after launch is the concern, How to Keep Your Software Secure After Launch covers the ongoing posture. For structured security review and ongoing hardening, see Application Security or get in touch.