A practical, in-depth comparison of the three most common API authentication approaches: API keys, JSON Web Tokens (JWT), and OAuth 2.0. Learn how each works, where each fits, the security pitfalls to avoid, and how to choose the right one for your API.
Summary: Every API needs an answer to two questions: who is calling? and what are they allowed to do? API keys, JWTs, and OAuth 2.0 are the three names you will hear most often, but they are not three versions of the same thing. They solve different problems at different layers. This article untangles them, shows how each works, compares them honestly, and gives you a practical framework for choosing and securing them.
A website has a human sitting behind a browser. An API has a program on the other end, which might be a mobile app, a server in another company, a script on a laptop, or an attacker running thousands of requests a minute.
APIs are also attractive targets. They expose data and actions directly, often with less friction than a user interface. A single weak endpoint can leak an entire database. Security teams consistently list broken authentication and broken authorization among the most common causes of API breaches.
The difficulty is made worse by confusing vocabulary. Developers often ask, "Should I use OAuth or JWT?" as though they were alternatives. They are not. By the end of this article, that question will make more sense, and you will see why the honest answer is often "both, for different jobs."
Two terms must be kept apart:
| Concept | Question it answers | Example |
|---|---|---|
| Authentication (AuthN) | Who are you? | Logging in with a password and a second factor |
| Authorization (AuthZ) | What are you allowed to do? | This user may read invoices but not delete them |
Many authentication problems in APIs are really authorization problems. A system can correctly identify a caller and still let them access another customer's data. This flaw is so common it has its own name: Broken Object Level Authorization (BOLA), consistently at the top of the OWASP API Security list.
Keep this distinction in mind as we look at each technique:
An API key is a long, random string issued to a developer or application. The client includes it with each request, and the server looks it up.
GET /v1/weather?city=Pokhara HTTP/1.1
Host: api.example.com
X-API-Key: sk_live_9fA3b7c1d2E4f6a8B0c2D4e6F8a0b2C4
You can think of it as a shared password for a program, with no user involved.
curl command.import secrets, hashlib
def create_api_key():
# 32 bytes of cryptographically secure randomness
raw_key = "sk_live_" + secrets.token_urlsafe(32)
# Store ONLY the hash; show raw_key to the user once
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
return raw_key, key_hash
Good practice includes:
sk_live_) so secret scanners can detect leaksA JSON Web Token (pronounced "jot") is a compact, self-contained, signed token that carries information, called claims, between parties. It is defined by the RFC 7519 standard.
The most important point: a JWT is a format, not a protocol. It tells you how to package and sign data. It does not tell you how to log in, how to obtain a token, or how to delegate access. That is where OAuth 2.0 comes in.
A JWT has three Base64URL-encoded parts separated by dots:
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
Header: states the signing algorithm and token type.
{ "alg": "RS256", "typ": "JWT", "kid": "key-2026-10" }
Payload: carries the claims.
{
"iss": "https://auth.example.com",
"sub": "user_8421",
"aud": "https://api.example.com",
"exp": 1791300000,
"iat": 1791296400,
"scope": "invoices:read profile:read"
}
Signature: created by signing the header and payload with a secret or private key. It lets the receiver verify the token was not altered.
Important: The payload is only encoded, not encrypted. Anyone who holds a JWT can read its contents. Never put passwords, secrets, or sensitive personal data in a standard (signed) JWT.
| Claim | Name | Purpose |
|---|---|---|
iss | Issuer | Who created the token |
sub | Subject | Who the token is about (usually a user ID) |
aud | Audience | Which service the token is intended for |
exp | Expiration | When the token stops being valid |
nbf | Not Before | Token invalid before this time |
iat | Issued At | When the token was created |
jti | JWT ID | Unique identifier, useful for revocation and replay prevention |
GET /v1/invoices HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi...
The API verifies the token without calling any database:
exp, nbf, iss, and aud.sub and scope.import jwt # PyJWT
def verify_token(token, public_key):
return jwt.decode(
token,
public_key,
algorithms=["RS256"], # explicit allowlist, never trust the header
audience="https://api.example.com",
issuer="https://auth.example.com",
options={"require": ["exp", "iss", "aud", "sub"]},
)
Never accept whichever algorithm the token header claims. Fixing the allowed algorithms server-side is essential.
A secure pattern uses two tokens:
Imagine you want a photo-printing app to access your pictures stored on a cloud service. In the bad old days, you would hand the printing app your cloud username and password. That meant the app could do anything with your account, and revoking its access meant changing your password for everything.
OAuth 2.0 (RFC 6749) solves this with delegated authorization: you grant the app limited access to specific resources, for a limited time, without sharing your password.
OAuth 2.0 is about authorization, not authentication. It answers "what may this app do on my behalf?" rather than "who is this user?"
| Role | Description | Example |
|---|---|---|
| Resource Owner | The user who owns the data | You |
| Client | The app requesting access | The photo-printing app |
| Authorization Server | Authenticates the user and issues tokens | The cloud provider's login server |
| Resource Server | The API that holds the data | The photo storage API |
photos:read or calendar:write, that limit what a token can do.Authorization Code Flow with PKCE (recommended for most apps)
The user is redirected to the authorization server, logs in, consents, and is sent back with a short-lived authorization code. The client exchanges that code for tokens. PKCE (Proof Key for Code Exchange) adds a one-time secret that prevents stolen authorization codes from being redeemed by attackers.
User/Browser Client App Auth Server API
| | | |
|-- Click "Login" ->| | |
| |-- code_challenge ->| |
|<------------ Redirect to login/consent ---------------|
|-- Log in & approve ------------------->| |
|<-- Redirect back with authorization code --------------|
|-- code ---------->| | |
| |-- code + code_verifier ----------->|
| |<-- access_token (+ refresh) -------|
| |-- Bearer access_token ------------>|
| |<-- protected data -----------------|
Client Credentials Flow
For machine-to-machine communication with no user involved. The client authenticates with its own credentials and receives a token for its own account.
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=billing-service
&client_secret=********
&scope=invoices:read
Device Authorization Flow
For input-constrained devices such as smart TVs. The device shows a code; the user approves it on a phone or computer.
Refresh Token Flow
Exchanges a refresh token for a new access token.
Deprecated flows to avoid:
OAuth 2.0 does not specify what an access token looks like. Two common approaches:
Since OAuth 2.0 does not tell the client who the user is, OpenID Connect (OIDC) was built on top of it to add standardized authentication.
OIDC adds:
sub, name, email, auth_time, and more).openid, profile, and email./.well-known/openid-configuration) that describes the provider's endpoints and keys.
A simple rule to remember:| Token | Meant for | Question answered |
|---|---|---|
| ID token | The client application | Who just logged in? |
| Access token | The API (resource server) | What may the caller do? |
A common mistake is sending an ID token to an API as though it were an access token. They have different audiences and purposes.
| Feature | API Keys | JWT | OAuth 2.0 |
|---|---|---|---|
| What it is | A shared secret string | A signed token format | An authorization framework |
| Primary purpose | Identify an application | Carry verifiable claims | Delegate limited access |
| Identifies users? | No (apps only) | Yes, if issued for a user | Yes, via tokens and OIDC |
| Granular permissions | Limited | Yes, through claims | Yes, through scopes |
| Expiry built in | Usually no | Yes (exp) | Yes (token lifetimes) |
| Easy revocation | Yes | Difficult | Yes (especially with opaque tokens) |
| Stateless | No (lookup needed) | Yes | Depends on token type |
| Implementation effort | Low | Medium | High |
| Third-party delegation | No | Not by itself | Yes, its core purpose |
| Typical risk | Leaked or hard-coded keys | Weak validation, theft | Misconfigured flows |
| Best for | Simple server-to-server, metering | Stateless service-to-service auth | User-delegated or multi-party access |
The key insight is that these are not competing options on the same axis:
Real systems layer these tools. Here are common patterns.
API key only. The provider wants to track and rate-limit developers. No private user data is involved.
OAuth 2.0 Client Credentials flow issuing short-lived JWT access tokens. Each service authenticates to the authorization server, receives a token with limited scopes, and presents it to other services, which verify the signature locally.
OAuth 2.0 Authorization Code + PKCE, with OIDC for login, and JWT or opaque access tokens for API calls. Users sign in once, grant scoped permissions, and can revoke them later.
API keys for developer onboarding and simple integrations, plus OAuth 2.0 for apps that act on behalf of end users. This is how many large platforms operate.
Any of the above plus additional protections: mutual TLS, request signing, step-up authentication, IP allowlists, and strict rate limits.
HttpOnly, Secure, SameSite) and CSRF protection matter.alg: none. Some early libraries treated unsigned tokens as valid.RS256 to HS256 and signs the token using the public key as the HMAC secret. Libraries that trust the header are vulnerable. Always enforce an algorithm allowlist.aud and iss. A token meant for service A gets accepted by service B.localStorage, where any cross-site scripting (XSS) bug can steal them. Prefer HttpOnly, Secure, SameSite cookies or a backend-for-frontend pattern for browser apps.kid header blindly, which can lead to injection or key-confusion attacks.state parameter, enabling CSRF on the login flow.Remember: the strongest authentication scheme is worthless if authorization checks are missing.
Transport
exp, nbf, iss, aud verified every timestate (and nonce for OIDC) validatedUse this decision guide as a starting point.
Question 1: Does the API need to know which user is acting?
| Scenario | Suggested Approach |
|---|---|
| Public weather or maps API | API keys with rate limiting |
| Mobile app logging users in | OAuth 2.0 Authorization Code + PKCE with OIDC |
| Single-page app | Backend-for-frontend with secure cookies, or Code + PKCE |
| Internal microservices | OAuth Client Credentials with short-lived JWTs, or mTLS |
| Third-party integrations with user data | OAuth 2.0 with narrowly scoped permissions |
| Webhooks | HMAC signature verification |
| Banking or healthcare APIs | OAuth 2.0 (such as FAPI profiles), mTLS, and sender-constrained tokens |
The question "OAuth 2.0, JWT, or API keys?" is a little like asking "Should I use a passport, a laminated card, or border control?" They live at different layers:
Four takeaways to carry forward:
| Term | Definition |
|---|---|
| Access token | A credential presented to an API to access protected resources |
| API key | A secret string identifying an application to an API |
| Authorization server | The server that authenticates users and issues OAuth tokens |
| Bearer token | A token that grants access to whoever holds ("bears") it |
| BOLA / IDOR | Broken Object Level Authorization / Insecure Direct Object Reference |
| Claim | A statement about a subject inside a token (for example, sub, exp) |
| Client | The application requesting access in OAuth |
| ID token | An OIDC JWT proving a user's authentication to the client |
| Introspection | Asking the authorization server whether a token is valid |
| JWKS | JSON Web Key Set; a published set of public keys for verifying tokens |
| JWT | JSON Web Token; a compact, signed token format |
| mTLS | Mutual TLS; both parties authenticate with certificates |
| OAuth 2.0 | A framework for delegated authorization |
| OIDC | OpenID Connect; an identity layer on top of OAuth 2.0 |
| Opaque token | A random token with no readable content |
| PKCE | Proof Key for Code Exchange; protects the authorization code flow |
| Refresh token | A long-lived credential used to obtain new access tokens |
| Resource server | The API that hosts protected resources |
| Scope | A named permission limiting what a token may do |
No comments yet. Start the conversation below.