API security deep dive – OAuth 2.0, OIDC, and JWT pitfalls – Secured Me

API security deep dive – OAuth 2.0, OIDC, and JWT pitfalls

How OAuth 2.0 and OpenID Connect actually work, the JWT pitfalls that bite even experienced teams, and a practical hardening checklist for modern APIs.

APIs are how modern systems actually talk to each other, and authentication is where most API security incidents start. OAuth 2.0 and OpenID Connect (OIDC) are now the lingua franca for delegated access and federated identity, and JSON Web Tokens (JWTs) are the format almost everyone reaches for. They are powerful but easy to misuse: the OWASP API Security Top 10 reads like a list of OAuth and JWT antipatterns. This article walks through how the protocols actually work, the pitfalls that bite even experienced teams, and a hardening checklist that closes most of them.

OAuth 2.0 in one diagram (in words)

OAuth 2.0 is an authorisation framework: it lets a client application get a token that grants it permission to access a resource on behalf of a user (or itself). The cast of characters:

The flow depends on the client. The current best-practice flows are:

Flows you should not use in new code: the Implicit flow (tokens delivered in URL fragments, vulnerable to leakage), the Resource Owner Password Credentials grant (the app collects the user's password directly — defeats the entire point of OAuth), and any home-grown variant that skips PKCE.

OIDC: authentication on top of OAuth

OAuth 2.0 was deliberately not designed for authentication ("who is the user"), only authorisation ("what can this token do"). OpenID Connect (OIDC) is the standard layer on top that adds authentication. The main addition is the ID Token: a signed JWT containing claims about the authenticated user (sub, email, name, aud, iss, iat, exp, nonce).

Key things to remember:

OIDC also gives you the discovery document (/.well-known/openid-configuration) and JWKS endpoint, which let clients fetch the auth server's public keys to verify token signatures.

JWT pitfalls that keep recurring

JWTs are everywhere because they are convenient: self-contained, signed, and stateless. They also have a long list of historical and active footguns.

A practical rule: use a maintained library (jsonwebtoken, jose, pyjwt, Microsoft.IdentityModel, etc.), pin the expected algorithm and audience explicitly, and never roll your own verification.

API authorisation: where the real bugs live

Even with perfect OAuth and JWT handling, the OWASP API Top 10 is dominated by authorisation failures inside your own code.

The fix is unglamorous: a single, centralised authorisation layer (policy engine, middleware) that checks "can this principal perform this action on this object?" on every request, not scattered if user.id == invoice.userId lines that one developer will forget.

A hardening checklist

A reasonable baseline for any production API:

Most API breaches do not require novel research. They exploit a missing audience check, a too-broad token scope, a leaked client secret in a repo, or an authorisation function that simply was not written. Getting the boring parts right — short tokens, strict validation, centralised authorisation — is what separates APIs that survive contact with the real world from those that don't.