API Authentication: API Keys, OAuth 2.0, and JWT Explained
Authentication is where most API integrations go wrong the first time. Not because the concepts are complicated, but because there are several distinct mechanisms in common use, they solve different problems, and developers often reach for the one they recognize rather than the one that fits. Understanding what each mechanism actually does — and what it does not do — is the difference between an integration that works and one that works until it doesn’t.
API Keys: Simple, Brittle, Everywhere
An API key is a long random string that identifies a caller. You include it in a request header, a query parameter, or occasionally a request body, and the server maps it to an account and its associated permissions. If the key is valid, the request proceeds. If not, you get a 401.
API keys are the simplest possible authentication mechanism and that simplicity is their main advantage. They require no authentication flow, no token exchange, and no session management. You copy the key, you put it in your request, it works. This is why every developer API — Stripe, SendGrid, OpenAI — starts with API keys for basic access.
The problems are also simple. API keys do not expire unless you build expiration logic. They do not carry identity beyond the account they map to. They cannot represent delegated access — you cannot give a third party limited access to your account using only a key without giving them the key itself, which is all-or-nothing. And they are frequently leaked: in public repositories, in client-side code, in logs.
For server-to-server communication where you control both sides, API keys are often entirely sufficient. For anything involving user-level permissions, delegated access, or third-party integrations, you need more structure.
OAuth 2.0: Delegated Access Done Right
OAuth 2.0 is not an authentication protocol — it is an authorization framework. The distinction matters. OAuth answers the question “can this application act on behalf of this user for these specific things?” It does not, by itself, tell you who the user is. (That is what OpenID Connect adds on top of OAuth 2.0, but that is a separate topic.)
The core OAuth 2.0 flow works like this: a user visits an application that wants access to their data on another service. The application redirects them to that service’s authorization endpoint. The user logs in and approves the requested permissions (scopes). The service issues an authorization code to the application. The application exchanges that code for an access token, which it then uses to make API calls on the user’s behalf.
The crucial property is that the user’s credentials never touch the application. The application receives a scoped, time-limited token — not a password. If the token is compromised, it can be revoked without changing the user’s password or affecting any other application. The user can review and revoke access at any time.
OAuth 2.0 has several grant types for different scenarios: the Authorization Code flow above for user-facing applications, the Client Credentials flow for machine-to-machine communication, the PKCE extension for mobile and single-page apps where a client secret cannot be safely stored. Each serves a different threat model.
The complexity of OAuth is genuine, not artificial. If you are implementing an API that third parties will integrate to access user data, OAuth is the right answer and there is no simpler one that is also correct.
JWT: A Token Format, Not an Auth Protocol
JSON Web Tokens are frequently misunderstood because they are not an authentication mechanism — they are a token format. A JWT is a base64-encoded, cryptographically signed JSON structure containing claims: statements about a subject, an issuer, an expiration time, and whatever application-specific data the issuer wants to include.
The key property is that a JWT can be verified without calling back to the issuer. The server receiving a JWT checks the signature against a known public key. If the signature is valid and the token has not expired, the claims can be trusted. No database lookup required.
This makes JWTs well-suited for distributed systems where you want stateless authentication. An API gateway can verify a JWT and pass downstream services the verified claims without those services needing to share a session store. This is architecturally clean and scales horizontally without coordination.
The tradeoffs: JWTs cannot be revoked before they expire. If a token is compromised, you cannot invalidate it — you wait for it to expire (which is why short expiration times, on the order of minutes, are strongly recommended for access tokens) or you build a token blocklist, which reintroduces the statefulness you were trying to avoid. JWTs also grow large if you put too many claims in them, and they travel in every request.
JWTs are commonly used as the access token format within OAuth 2.0 flows. The concepts layer: OAuth handles the delegation and grant flow; JWTs provide a self-contained, verifiable format for the resulting tokens.
Choosing the Right Mechanism
For simple server-to-server integration where you control both sides: API keys, rotated regularly and stored in environment variables, not code.
For user-facing applications where a third party needs access to user-owned resources: OAuth 2.0, Authorization Code flow with PKCE if you have a public client.
For stateless, high-throughput authentication across internal services: JWTs with short expiration times, issued by a central auth service.
For any system exposed to the internet: HTTPS everywhere, always. None of these mechanisms survive being sent over plaintext.
Authentication is not the interesting part of building an API. Getting it right means it stays invisible. Getting it wrong means it becomes the only thing anyone talks about.