AllAboutAPIs

REST API Security Best Practices in Production

Authorization failures, not authentication, cause most API breaches—here's how to stop them.

Reporter · · 9 min read
Cover illustration for “REST API Security Best Practices in Production”
Auth & Security · July 31, 2026 · 9 min read · 2,103 words

OWASP's API Security Top 10 is not a compliance checklist. It's a record of what actually happened. You read through it and the same thought keeps surfacing: how did this get missed? The answer, almost every time, is that someone assumed another layer had already handled it.

Three of the top five risks are authorization failures. Not authentication. Authorization. Developers conflate the two, and that confusion is where most breaches live.

Here's what you actually need to know by name.

API1. Broken Object Level Authorization (BOLA). A caller requests /orders/1042. The server returns it because the ID exists. Nobody checked whether this caller owns order 1042. That's the whole vulnerability. Not exotic. Not clever. Just missing.

API2. Broken Authentication. Weak tokens, sloppy token handling, implementation bugs that let attackers impersonate real users. Broken authentication showed up behind more than half the API breaches in a large recent analysis.

API3. Broken Object Property Level Authorization (BOPLA). Two flavors. Your API either gives back more fields than the caller should see, or it accepts more fields than they should write. Both happen constantly. A response with an unexposed admin flag sitting in the JSON is BOPLA. An endpoint that accepts "isAdmin": true in the body and maps it straight to the model is BOPLA.

API6. Unrestricted Access to Sensitive Business Flows. The endpoint works perfectly. It just has no controls around how fast or how many times it can be called. A "verify OTP" endpoint with no throttling isn't broken. It's just open season.

API7. Server-Side Request Forgery (SSRF). Your API fetches a URL the caller supplies. The attacker supplies an internal IP. Your server helpfully retrieves it from behind the firewall, because why wouldn't it?

API8. Security Misconfiguration. Default settings left on. Error messages that explain your stack to anyone who triggers one. Debug endpoints that never got turned off. This is usually the lowest-effort entry point.

API9. Improper Inventory Management. Shadow APIs. Deprecated endpoints still running in production. You can't protect what you haven't catalogued, and this one is embarrassingly common.

API10. Unsafe Consumption of APIs. Attackers compromise your system through a third-party integration. Nearly a third of breaches in recent analysis traced back here.

What ties these together is a single pattern: assumptions about what's already secured, applied confidently in the wrong direction. The rest of this piece works through each major defense layer with that pattern as context.

Getting authentication right with OAuth 2.0, OpenID Connect, and JWTs

OAuth 2.0 with OpenID Connect is the current industry standard. A centralized auth server gives you consistent token issuance, cleaner revocation, and a single audit trail. Don't build your own. The failure modes in homegrown auth systems are well-documented and almost always predictable.

JWTs are everywhere. They're also widely misunderstood in ways that produce real vulnerabilities.

The format itself is not the issue. Security comes entirely from how you issue and validate them.

What validation must always do:

  • Verify the signature. Every time. Not "most of the time."
  • Check iss (issuer) and aud (audience) claims explicitly.
  • Enforce expiration (exp). Never accept a token past its expiry.
  • Hard-code the expected algorithm in your verification logic. Never read it from the token header.

That last point is where a lot of teams get caught.

The algorithm confusion problem

With symmetric HS256, every service that validates tokens shares the same secret. In a microservices setup, one compromised service exposes the signing key across your entire system. RS256 or ES256 solves this cleanly. The auth service holds the private key. Every downstream service verifies using the public key only. Compromise a downstream service all you want. The signing key isn't there.

There's also a specific attack worth knowing. An attacker changes the alg header in the JWT from RS256 to HS256, then signs a forged token using your server's public key as the HMAC secret. If your verification code reads the algorithm from the token header to decide which method to use, the attack works. This is not theoretical. It has caused real breaches.

Hard-code the expected algorithm. And never, ever accept "alg": "none". The JWT spec technically includes an unsecured mode. Real vulnerabilities have come from libraries that honored it in production.

Token lifetime and where tokens live

Keep access token lifetimes short. Five to fifteen minutes is the industry guidance. A stolen token with a short window does limited damage. Refresh tokens are longer-lived, which makes them more sensitive. A compromised refresh token can sustain unauthorized access indefinitely without triggering re-authentication. Treat them accordingly.

For web apps, store JWTs in Secure + HttpOnly cookies rather than localStorage. The HttpOnly flag means JavaScript can't read the token directly, which cuts off the cleanest token-theft path from XSS. It doesn't make XSS harmless. It just removes one easy win.

One thing people miss: the JWT payload is Base64URL-encoded, not encrypted. Anyone who has the token can decode it instantly. Don't put passwords, API keys, PII, or anything sensitive in the payload. Identifiers and roles only.

Apply the same authentication standards to internal service-to-service calls as you do to external ones. "Internal" is not a trust boundary. It's just a network segment.

Authorization failures are the most common breach path and the hardest to retrofit

Authentication answers "who are you?" Authorization answers "what are you allowed to do?" Most of the OWASP top five lives in the space between those two questions, and that space is where retrofitting gets painful.

BOLA is the simplest example and somehow still the most common. A user requests an object by ID. The server returns it without checking whether that user owns it. The fix is a server-side ownership check tied to the authenticated identity. Every object reference. Every time. There is no clever shortcut here.

The two faces of BOPLA

Excessive data exposure happens when your API returns the full object and expects the client to decide what to display. The user sees a clean UI. An attacker reads the raw response and finds fields the UI never shows them.

Mass assignment happens when your API accepts a full JSON body and maps it directly to a data model. An attacker includes "isAdmin": true. The model picks it up. Privilege escalation, done.

Both have the same fix. Define explicit allow-lists for what each endpoint returns and accepts. Never expose or bind the full object by default. This sounds obvious until you're working in a codebase where someone wired up a generic resource handler six months ago and nobody looked at it since.

RBAC, ABAC, and not giving people more than they need

Role-Based Access Control is the standard baseline. Users inherit permissions through their roles. Manageable, well-understood, and the right starting point for most systems.

Attribute-Based Access Control goes further. It evaluates policies against attributes of the user, the resource, and the request context at runtime. For multi-tenant APIs where access decisions are genuinely contextual, ABAC gives you the granularity RBAC can't. It also adds complexity, which is a real tradeoff.

Either way: least privilege. Grant only what the client actually needs for its function. Review it periodically. Remove what isn't used.

Authorization logic lives on the server. Never trust client-supplied roles or flags in the request body. That sentence should be obvious. It apparently isn't.

Authorization at the workflow level

OWASP API6 is authorization applied to business flows rather than individual resources. Volkswagen's MyVW app had an OTP verification endpoint with no rate limiting. A brute-force script could cycle through codes and take over accounts. The endpoint worked exactly as designed. The controls around how it could be used just weren't there.

This is harder to retrofit than technical controls because business logic varies by product and changes constantly. The time to think about it is before launch.

Transport security and the role of mTLS in zero-trust service meshes

All API traffic needs HTTPS. TLS 1.3 is preferred. TLS 1.2 is the minimum. This eliminates interception, replay attacks, and in-transit modification. Non-negotiable.

Standard TLS authenticates the server to the client. For internal service-to-service traffic, that's often not sufficient.

Why mTLS matters inside the perimeter

Mutual TLS means both parties present certificates and verify each other before any data moves. It's the right approach for microservices architectures where you control both ends of the connection. It prevents impersonation of internal services. It stops man-in-the-middle attacks on internal traffic. It fits naturally into a zero-trust posture, which says that being on the internal network does not mean you're trusted.

The honest operational reality: mTLS adds real overhead. Certificate rotation, CA trust distribution, handling expiry gracefully. These are genuine costs, not just footnotes. Go in with a clear picture of what you're taking on. The security gain is real, but so is the maintenance burden.

At the gateway layer, redirect all HTTP to HTTPS. Don't silently accept plaintext in production. If your API has any browser-facing surface, set the HSTS header. It tells browsers to refuse future plaintext connections to your origin.

Input validation, output filtering, and the injection risks they prevent

Every field, parameter, and header coming into your API is a potential attack surface until you've validated it. Path traversal is the most common API vulnerability by category, followed closely by SQL injection. Both are preventable with server-side validation. Both keep showing up anyway.

What validation needs to cover:

  • Type. Reject values that don't match the expected type before they reach any business logic.
  • Format. Validate against a schema, your OpenAPI spec, JSON Schema. Reject malformed structures immediately.
  • Length and range. Enforce maximum string lengths. Reject values outside expected numeric ranges.
  • Allow-listing over deny-listing. Define what's permitted. Trying to enumerate everything that's forbidden is a losing strategy. Deny-lists always have gaps.
  • Encoding. Normalize and decode input before you validate it. URL-encoded or Unicode-encoded payloads can slip through checks applied to the raw string.

Output filtering

Output filtering is the mirror of input validation. It's equally important and gets less attention.

Never return more fields than the caller is authorized to see. Define explicit response shapes per endpoint and per role. Don't return the full object and trust that nobody will notice the extra fields.

Strip stack traces, internal system details, and debug information from production error responses. Verbose errors are OWASP API8 in action. They give attackers a detailed map of your architecture. An error message should confirm something went wrong. It should not explain what to try next.

SSRF and SQL injection

If any endpoint accepts a user-supplied URI and fetches it server-side, you need an allow-list of permitted destinations. Block outbound requests to private IP ranges (10.x, 172.16-31.x, 192.168.x, 169.254.x) before the call is made. The attacker is betting your server will helpfully retrieve internal resources on their behalf. Don't.

For SQL injection: parameterized queries or prepared statements, everywhere, without exception. ORM usage doesn't automatically protect you if raw query construction gets mixed in anywhere. That happens more often than people admit.

Rate limiting and throttling as controls against automated abuse

Nearly a third of API incidents involve automated attack tools. DDoS, brute force, credential stuffing, fraud at scale. All three rely on the same condition: automation running unchecked against an API that has no enforcement.

The practical implication is that rate limiting is not primarily a performance control. It's a security control.

Enforce limits by client identity, not IP address. IP-based limits fail against distributed botnet traffic and against legitimate multi-tenant clients sharing cloud egress IPs. Enforce per API key, per OAuth client ID, or per authenticated user identity at the gateway layer.

Rate limiting and quotas are different tools

Rate limiting caps requests per second or minute. It handles burst traffic and brute-force velocity.

Quotas cap requests per day or month. They handle sustained overuse and cost exposure on metered APIs.

Apply both. They solve different problems. Using one doesn't make the other redundant.

Tighter limits on sensitive flows

Endpoints that trigger sensitive operations need stricter per-user limits than general read endpoints. OTP verification. Checkout. Password reset. These are high-value targets for attackers, and they're also expensive to operate at scale if someone is hammering them.

The VW MyVW example is useful here. No throttling on a code verification endpoint meant a script could cycle through every possible value until one worked. Tight per-user limits on that specific flow would have made that attack impractical.

When you do throttle a client, return 429 Too Many Requests with a Retry-After header. Not 200. Not a silent drop. Legitimate clients need to know what happened and when they can try again. That transparency costs you nothing and helps everyone except the person trying to abuse the endpoint.

Sources

  1. aikido.dev
  2. pynt.io
  3. stackoverflow.blog
  4. gravitee.io
  5. akamai.com
  6. cheatsheetseries.owasp.org
  7. curity.io
  8. duendesoftware.com
Filed underAuth & Security

More in Auth & Security