API Security Best Practices for 2026
Most API breaches are not clever. They are an endpoint that checked whether you were logged in but never checked whether the record was yours, or a token that stayed valid for a month, or a key that went into git in 2023 and is still live. The exotic attacks get the conference talks; the boring ones get the data. This is the list that actually prevents incidents, ordered by how often each item is the cause rather than by how interesting it is.
On this page
Authorisation, the one that matters most
Broken object level authorisation sits at the top of the OWASP API Security Top 10 and it deserves the position. The pattern is always the same: a route authenticates the caller, then loads a record by the id in the path without asking whether that caller is entitled to it. Change 1042 to 1043 and you have somebody else's invoice. No scanner finds it reliably, because from the outside it looks like a normal successful response.
The fix is architectural rather than clever. Never load by id alone; load by id scoped to the caller, so the ownership check is part of the query rather than a separate line somebody can forget. Push the rule into a shared layer, then write a test per resource that asks for another tenant's record and expects a 404. That test suite is worth more than any amount of penetration testing after the fact.
Two relatives of the same bug are worth naming. Broken function level authorisation is when an ordinary user can call an admin route because the check lives in the interface rather than the API. And missing authorisation on the second step is when the first call is guarded and the follow up, the export or the bulk endpoint, is not.
Tokens and keys
Decide what each credential is for and stop mixing them. An API key identifies a machine and belongs in server to server traffic. It must be scoped to the minimum set of operations, rotatable without downtime, and never shipped anywhere a browser can read it. A user token carries identity and belongs in a short lived access token with a refresh token behind it.
The recurring mistake is treating a JWT as a session. A signed token cannot be revoked once issued, so if the access token lives for a week then a stolen one lives for a week too. Keep access tokens in the minutes, keep refresh tokens revocable and stored server side, and rotate the refresh token on every use so a replayed one is detectable. Validate the signature, the issuer, the audience and the expiry on every request, and reject any token whose algorithm header you did not expect.
- Scope every key and token to the least privilege that works.
- Access tokens in minutes, refresh tokens revocable, rotation on use.
- Never put tokens in query strings, where they land in logs and referrer headers.
- Validate signature, issuer, audience, expiry and algorithm, every time.
- Make rotation a routine operation, not an incident response.
How you shape the contract matters here too. A single flexible endpoint is a larger authorisation surface than several narrow ones, which is one of the practical trade offs behind our comparison of REST and GraphQL.
Input, output and mass assignment
Validate on a schema at the boundary and reject anything that does not match, rather than sanitising as you go. Parameterised queries remove injection as a category, and no amount of escaping by hand is equivalent. That much is well known. The part teams still get wrong is the two sided nature of the problem.
Mass assignment is the input half. If you bind a request body straight onto a model, a caller can send fields you never intended to expose, and the classic result is a request that quietly sets a role to admin. Accept an explicit list of fields, never the whole object.
Excessive data exposure is the output half. Returning the full record and letting the client show three fields means the other twenty are one browser inspector away. Serialise deliberately, per endpoint, and treat internal identifiers, hashes and audit fields as things to omit rather than things to include by default.
Rate limiting and abuse
Rate limit per authenticated identity rather than per IP address. IP limits punish everyone behind a corporate network and miss any attacker with a modest pool of addresses. Layer the limits: a global ceiling, a tighter one on authentication and password reset, and a much tighter one on anything that sends a message, generates a document or costs you money on a third party service.
Return 429 with a Retry-After header so honest clients back off correctly instead of hammering. Consider a cost based budget rather than a request count for endpoints where one call can be far more expensive than another, which is the usual situation for search, reporting and anything backed by a language model. AI endpoints deserve special attention here, because abuse is not just load, it is a direct bill, a point we expand on in AI in software development.
Secrets and dependencies
Secrets belong in the environment or a managed store, and secret scanning belongs in continuous integration so that a leaked key fails the build rather than surfacing in a report months later. Anything that ever reached git history should be considered compromised even after the commit is rewritten, because clones and caches do not forget.
Dependencies are the other half. Pin versions, keep a lockfile, run automated vulnerability scanning on every build, and treat a transitive dependency with the same suspicion as a direct one. The practical requirement is not zero vulnerabilities, which is unattainable, but a short and rehearsed path from disclosure to deployed patch. That path is a delivery pipeline problem more than a security one, which is why it belongs in the same conversation as the rest of your backend platform.
Logging and knowing you were hit
The median breach is not discovered by the team that was breached. Log authentication outcomes, authorisation denials, rate limit trips and every administrative action, with enough context to reconstruct a sequence, and alert on patterns rather than on individual events. A single 403 is noise. Two hundred 403s from one token in a minute is an enumeration attempt.
Log carefully as well as thoroughly. Tokens, passwords, card numbers and personal data do not belong in application logs, and the fastest way to create a breach is to write the credential into the same system you gave the whole engineering team access to. Redact at the logging layer so it cannot be forgotten at a call site.
The short checklist
- Every read and write is scoped to the caller in the query itself, with a test proving it.
- Access tokens are short lived, refresh tokens are revocable, keys are scoped and rotatable.
- Requests are validated against a schema; responses are serialised field by field.
- No binding of raw request bodies onto models.
- Rate limits per identity, tighter on auth and on anything expensive, with correct 429 responses.
- TLS everywhere, HSTS on, no credentials in query strings.
- Secrets outside the repository, scanning in continuous integration, rotation as routine.
- Dependency scanning on every build and a rehearsed patch path.
- Security relevant events logged, alerted on by pattern, with sensitive fields redacted.
- Errors that say what went wrong without revealing the stack, the query or whether an account exists.
Questions people ask
What is the most common API security failure?
Broken object level authorisation. The endpoint verifies that you are logged in but not that the record belongs to you, so changing an id returns someone else's data.
API keys or JWTs?
Keys identify machines and suit server to server traffic when scoped and rotatable. JWTs carry user identity and suit short lived access tokens with a refresh token behind them. Do not use a long lived JWT as a session, because you cannot revoke it.
How should rate limiting be applied?
Per authenticated identity rather than per IP, strictest on authentication and on anything that costs money, and always with a proper 429 and Retry-After.
Is HTTPS enough?
No. It protects data in transit and nothing else. Almost every real breach happens over a valid TLS connection.
How do you keep secrets out of a codebase?
Environment or managed secret store, scanning in continuous integration so leaks fail the build, scheduled rotation, and treating anything that reached git history as already compromised.
More development guides
Comparisons and how-tos on building and running modern software and APIs.
Read the blog