Securing Your APIs: Authentication, Authorization, and Best Practices
Complete guide to API security: OAuth 2.0, JWT, rate limiting, and the OWASP API Security Top 10.
APIs are the doors and windows of modern applications — and attackers know it. Studies consistently show that a large share of web attacks now target APIs directly, precisely because they expose business logic without a human-facing interface to slow things down. This guide walks through the three pillars of API security — authentication, authorization and operational hardening — with concrete practices you can apply this week.
Authentication: who are you?
Authentication for APIs comes in three common flavors:
- API keys — simple static tokens. Fine for low-risk, server-to-server use, but they leak easily (committed to repos, embedded in front-ends) and can't carry fine-grained permissions. Treat them as long-lived passwords: store hashed, rotate, and never ship them to browsers.
- JWT (JSON Web Tokens) — signed tokens carrying claims. Great as short-lived access tokens, but full of foot-guns: accept only tokens signed with a strong algorithm (explicitly
RS256/ES256, neveralg: none), validate the issuer and audience, and keep lifetimes short (5–15 minutes). - OAuth 2.0 / OIDC — the delegation standard. Use the Authorization Code flow with PKCE for anything involving users; client credentials for machine-to-machine. Never use the implicit flow in new code.
// Server-side: verify, don't trust
const claims = jwt.verify(token, publicKey, {
algorithms: ["RS256"], // pin the algorithm
issuer: "https://auth.example.com",
audience: "api.example.com",
clockTolerance: 5,
});
Authorization: what may you do?
Authentication without authorization is a bouncer who checks ID and then lets everyone backstage. The classic API bug is BOLA/IDOR (Broken Object Level Authorization): endpoint /api/orders/1042 returns any order to any logged-in user. The fix is boring and essential — check ownership on every object access:
def get_order(order_id, user):
order = db.get(Order, order_id)
if order is None or order.user_id != user.id:
raise NotFound() # don't leak existence
return order
Prefer non-sequential identifiers (UUIDs) where enumeration matters, but treat them as defense-in-depth — never as the access control itself. Also apply function-level checks: can this user call the admin variant of this endpoint at all?
OWASP API Security Top 10 essentials
The OWASP API Security Top 10 is the checklist I run against any API. The highest-impact items:
- BOLA — object-level authorization on every read/write (see above).
- Broken authentication — weak token validation, missing rotation, credentials in URLs.
- Broken object property level authorization — endpoints returning more fields than the UI needs (password hashes, internal flags). Whitelist response fields.
- Unrestricted resource consumption — no rate limits, unbounded pagination, huge upload endpoints.
- Unrestricted business flows — automation abuse of legitimate flows (e.g., unlimited discount-code checks).
- SSRF — endpoints that fetch URLs supplied by the client can be aimed at your internal network.
- Security misconfiguration — verbose errors, CORS
*with credentials, debug endpoints left enabled.
Defense in depth
- Rate limiting per key, per IP and per account — return
429withRetry-After, not silence. - Input validation with explicit schemas (size limits, types, enums). Reject early, fail closed.
- TLS everywhere, HSTS on the domain, no exceptions for "internal" endpoints.
- Logging of auth failures, authorization denials and validation errors — with alert thresholds. Attackers probe; your logs should notice.
- Least-privilege service accounts and secret rotation on a schedule.
Testing your own API
Security you haven't tested is a hypothesis. In an authorized test I check, at minimum: auth bypass on every object endpoint, token tampering (alg swap, expired replay, audience confusion), rate-limit behavior, error verbosity, and SSRF on any URL-accepting parameter. Automate the regression cases in CI so fixes stay fixed.
The five-minute audit
Not sure where your API stands? Run this five-minute self-audit before reading anything else:
- Pick one authenticated endpoint that returns an object by ID. Change the ID to another user's resource while logged in as a different user. Does it return data? (BOLA — the #1 finding, every year.)
- Send a request with no token, then with a tampered token (change one character of the signature). Are both rejected with 401/403 — or does something interesting happen?
- Hit a lightweight endpoint 100 times rapidly. Do you get 429s, or does the server just… keep serving? (No rate limiting is an open invitation.)
- Trigger a validation error with garbage input. Does the response expose a stack trace, SQL fragment or framework version?
- Check
/openapi.json,/docs,/.env— are debug or config endpoints exposed in production?
If any of the five reveals a problem, you now know exactly where to start — and it usually isn't with exotic attack techniques.
Key takeaways
- Authorization failures (BOLA) beat exotic exploits as the #1 API risk — check ownership everywhere.
- Short-lived JWTs + refresh flow; pin signing algorithms; validate iss/aud.
- Rate-limit, validate, log, alert — operational controls catch what code review misses.
- Test authorization like an attacker, and re-test after every release.
Want a professional, authorized security review of your API? Contact me — certified penetration testing, documented findings, prioritized fixes.

