Editorial illustration: OAuth 2.0 and JWT for Small APIs: A Pragmatic Security Guide

API Security · OAuth 2.0 · JWT · Authentication · Small APIs · API Development

OAuth 2.0 and JWT for Small APIs: A Pragmatic Security Guide

A practical guide to choosing and implementing OAuth 2.0 and JWT-based authentication for small public APIs — understanding grant types, token lifecycle management, and avoiding common security mistakes without over-engineering.

Published:

The Short Answer

OAuth 2.0 is an authorization framework; JWT is a token format. They solve different problems and are often used together, not as alternatives. For a small public API, use OAuth 2.0 with the Authorization Code flow (plus PKCE for public clients) to handle the authentication and consent process, and use opaque access tokens when exposing your API externally. Reserve JWTs for internal service-to-service communication where you need self-contained claims. This approach keeps your security model simple, your tokens revocable, and your external surface area minimal.

Why This Matters for Small Teams

Most indie developers and small teams reach for JWTs because they are easy to implement and require no server-side session storage. The token is self-contained — decode it, check the signature, and you know who the user is. That convenience comes with real trade-offs that become expensive when you need to revoke access, rotate credentials, or protect sensitive user data.

OAuth 2.0, by contrast, is deliberately more complex. It separates authentication from authorization, introduces grant types for different client contexts, and gives you control over token lifecycle. For a small API that exposes public endpoints, the extra structure pays off: you get standardized flows, you avoid leaking user data into tokens, and you maintain the ability to revoke access without invalidating every issued token.

OAuth 2.0 Fundamentals: The Four Actors

OAuth 2.0 defines four roles that map to real-world components in your system:

Keeping these roles separate is not academic — it is the single most important design decision you will make. When your authorization server issues tokens and your resource server validates them independently, you can rotate signing keys, change token formats, and update policies without touching the API endpoints that consumers actually call.

Choosing the Right Grant Type

OAuth 2.0 defines several grant types. For a small public API, you will typically need only two:

Authorization Code Flow is the standard for confidential clients — server-side applications that can securely store a client secret. The flow redirects the user to the authorization server, where they consent to the requested scopes. The server then issues an authorization code, which your backend exchanges for an access token. This is the flow behind “Sign in with Google” and every major OAuth integration.

Authorization Code Flow with PKCE is the equivalent for public clients — single-page applications, mobile apps, or any client that cannot securely store a secret. PKCE (Proof Key for Code Exchange) adds a code_challenge and code_verifier pair to prevent authorization code interception attacks. If your API has a frontend that calls it directly from a browser, use PKCE. Do not use the implicit flow — it is deprecated and insecure.

The Client Credentials Flow is for machine-to-machine communication where there is no user involved. If your API is called by another service using a static client ID and secret, this is the appropriate grant type.

JWT vs. Opaque Tokens: The Real Trade-off

This is where most small teams make a costly mistake. JWTs are by-value tokens — the token itself contains all the information needed to validate it. Opaque tokens are reference tokens — the token is a random string that the resource server must look up to determine what it grants.

The dossier sources are clear on this point: when tokens are exposed outside your infrastructure, especially to third-party clients, you should use opaque tokens instead of JWTs. JWT data is easy to decode. Base64 is not encryption. If you put user information in a JWT access token, every client developer can read it. And they will.

Opaque tokens solve this problem. The token reveals nothing about the user. The resource server validates it against the authorization server, and the client never sees the claims. This is the recommended approach for public APIs.

Use JWTs internally — between your microservices, between your authorization server and your resource servers. In that context, the self-contained nature of JWTs is a genuine advantage: services can validate tokens without a round-trip to a central database, which improves performance and reduces latency.

Token Lifecycle Management

Tokens have a finite lifespan, and managing that lifespan is where security lives or dies.

Access tokens should be short-lived — typically minutes, not hours. A short expiry limits the damage from token theft. If an access token is leaked, the attacker has only a narrow window.

Refresh tokens are longer-lived and are used to obtain new access tokens without requiring the user to re-authenticate. Refresh tokens should be stored securely on the client side and rotated on each use. Rotation means issuing a new refresh token alongside the new access token and invalidating the old one. This limits the impact of a refresh token leak.

Revocation is the hardest problem. With opaque tokens, revocation is straightforward — delete the token record from your store. With JWTs, revocation requires a blocklist or short lifetimes, because the token is self-contained and the resource server cannot check with the issuer on every request without defeating the performance benefit.

For a small API, the pragmatic approach is: short-lived access tokens (5–15 minutes), rotated refresh tokens, and opaque tokens for external clients. This gives you revocability without the complexity of a JWT blocklist.

Common Security Mistakes to Avoid

Mistake 1: Storing tokens in localStorage. JavaScript running in the browser can read localStorage. Use httpOnly cookies or in-memory storage instead. This prevents XSS attacks from stealing tokens.

Mistake 2: Putting sensitive claims in JWTs. User IDs and roles are fine. Email addresses, phone numbers, and any PII should not be in access tokens. If you need identity information, use an ID token (which is a JWT by specification) and keep it separate from the access token.

Mistake 3: Skipping the state parameter. The state parameter in the authorization request prevents CSRF attacks during the OAuth flow. Always include a cryptographically random state value and validate it on the callback.

Mistake 4: Using HS256 with a short secret. HMAC-based JWT signing requires a secret that is at least as long as the hash output. For RS256, use a proper key pair. Never use a weak or short secret for token signing.

Mistake 5: Issuing tokens from your API instead of a centralized authorization server. The dossier sources explicitly recommend against this. Token issuance requires authenticating the client, authenticating the user, authorizing the client, and signing tokens — all operations that require access to different data stores. A single centralized authorization server is the only safe approach.

A Minimal Implementation Path

For a small team building a public API, here is the path that avoids over-engineering while maintaining security:

  1. Use a proven open-source authorization server (Keycloak, Auth0, or a similar solution). Do not build your own.
  2. Configure the Authorization Code flow with PKCE for browser-based clients.
  3. Issue opaque access tokens to external clients.
  4. Use JWTs only for internal service-to-service communication.
  5. Set access token lifetime to 5–15 minutes.
  6. Implement refresh token rotation.
  7. Validate the state parameter on every authorization request.
  8. Put your API behind a gateway that handles rate limiting, logging, and token validation.

This is not the most flexible architecture. It is the one that will keep your API secure while you focus on building features. You can always add complexity later — but you cannot easily remove security debt.

FAQ

Q: Can I use JWTs for everything and skip OAuth entirely? A: You can, but you will lose the standardized consent flow, the separation of concerns between authorization and resource servers, and the ability to use existing identity providers. JWT-only authentication is fine for private internal APIs where you control all clients. It is a poor choice for public APIs.

Q: How do I revoke a JWT if it has been leaked? A: You cannot revoke a JWT without a blocklist or by making it expire. This is the fundamental trade-off. Short-lived tokens (5–15 minutes) make blocklists unnecessary in most cases. If you need immediate revocation, use opaque tokens.

Q: Is OAuth 2.0 authentication or authorization? A: OAuth 2.0 is strictly an authorization framework. It answers “what can this app access?” not “who is this user?”. If you need to identify the user, add OpenID Connect on top of OAuth 2.0. OIDC provides an ID token that carries authenticated user information.

Q: Do I need an API gateway for a small API? A: Not strictly, but the dossier sources recommend it strongly. A gateway centralizes security concerns — rate limiting, logging, token validation, and request filtering — so your API endpoints can focus on business logic. For a small team, this separation of concerns is worth the overhead.

Q: What about OAuth 2.1? A: OAuth 2.1 is a proposed simplification of OAuth 2.0 that removes deprecated grant types, mandates PKCE, and tightens security requirements. It is not yet a finalized standard, but the direction is clear: the industry is moving toward stricter, simpler OAuth. Building with current best practices now positions you well for that transition.

Sources