API Authentication
Mechanisms for establishing identity at API boundaries — OAuth 2.0 flows (authorization code + PKCE, client credentials, device code), API keys, JWT structure and local validation, mTLS for service-to-service identity, and token introspection.
What it is
API authentication is the process of verifying the identity of the entity (user, service, or device) making an API request. Authentication answers "who are you?" — it does not answer "what can you do?" (that's authorisation). For APIs, the challenge is authenticating stateless HTTP requests efficiently and securely at scale.
The major authentication mechanisms for APIs are: API keys (simple opaque tokens), JWT (JSON Web Tokens) (self-contained, signed tokens carrying claims), OAuth 2.0 (a delegated authorisation framework with multiple flows for different use cases), and mTLS (mutual TLS) (certificate-based mutual authentication, primarily for service-to-service). Each mechanism has specific use cases, security properties, and operational trade-offs.
Why it exists
HTTP is stateless — every request must carry its own credentials. Simple username/password per request is insecure (password exposure, no scope limitation) and inflexible (can't grant limited access to third parties). The evolution from Basic Auth → API keys → OAuth 2.0 reflects increasing requirements: multi-factor authentication, delegated third-party access, fine-grained scope limitation, and token revocation without requiring password changes.
When to use
- OAuth 2.0 Authorization Code + PKCE — any user-facing application (web SPA, mobile app) where a human grants access to their data.
- OAuth 2.0 Client Credentials — machine-to-machine: a service authenticating with another service with no human in the loop.
- OAuth 2.0 Device Code — devices with limited input capabilities (smart TVs, CLI tools) that can't open a browser.
- API keys — third-party developer access where full OAuth flow is unnecessary; server-to-server calls where the key is stored securely in secrets management.
- JWT (local validation) — high-volume APIs where the overhead of introspection requests per API call is prohibitive.
- mTLS — internal service-to-service communication in a zero-trust network; customer-to-service in high-security (banking, financial) contexts.
When not to use
- API keys in browser-side code — API keys embedded in JavaScript are exposed to anyone who reads the source.
- Long-lived JWTs without refresh token rotation — a stolen JWT remains valid until expiry; short expiry + refresh token reduces the window.
- OAuth Implicit flow — deprecated; access tokens in URL fragments are logged in browser history and server logs; use Authorization Code + PKCE instead.
Typical architecture
Authorization Code + PKCE (user-facing SPA/mobile):
Browser/App
1. Generate code_verifier, code_challenge = SHA256(verifier)
2. Redirect to /authorize?response_type=code
&code_challenge=...&code_challenge_method=S256
Auth Server
3. User authenticates, grants consent
4. Redirect to app with ?code=AUTH_CODE
App
5. POST /token { code, code_verifier, client_id }
Auth Server
6. Verify code_verifier matches stored challenge
7. ← { access_token, refresh_token, id_token }
Client Credentials (M2M):
Service A
POST /token { grant_type=client_credentials,
client_id, client_secret }
Auth Server ← { access_token, expires_in }
Service A → GET /api/resource + Bearer token
JWT Structure:
Header.Payload.Signature (Base64URL encoded, dot-separated)
Header: { "alg": "RS256", "typ": "JWT", "kid": "key-id-1" }
Payload: {
"sub": "user-42",
"iss": "https://auth.example.com",
"aud": ["https://api.example.com"],
"iat": 1714000000,
"exp": 1714003600,
"scope": "orders:read profile:read"
}
Signature: RSA-SHA256(base64url(header) + "." + base64url(payload), private_key)
Local JWT Validation (no network call):
1. Fetch JWKS from iss/.well-known/jwks.json (cached, refreshed on kid miss)
2. Verify signature using public key matching kid header
3. Verify iss, aud, exp, nbf claims
4. Extract sub, scope, custom claims
Token Introspection (RFC 7662 — real-time):
Resource Server → POST /introspect { token } → Auth Server
Auth Server ← { active: true, sub, scope, exp }
(adds latency; use for high-value ops or immediate revocation)
Pros and cons
Pros
- OAuth 2.0 enables delegated access without sharing passwords — third-party apps get limited, revocable tokens.
- JWT local validation scales to millions of requests per second — no auth server lookup per request.
- PKCE eliminates the authorisation code interception attack without requiring a client secret on public clients.
- mTLS provides mutual identity verification — both client and server prove their identity, preventing impersonation.
- Scoped tokens enforce least privilege at the token level — a token for
orders:readcan't mutate users.
Cons
- JWTs cannot be revoked before expiry without an additional revocation list or introspection call.
- OAuth 2.0 complexity — 6+ grant types, multiple token types, dozens of RFCs; easy to implement incorrectly.
- API keys provide no scoping, expiry, or user identity by default; rotate them infrequently at your peril.
- mTLS requires PKI management (certificate rotation, CA trust chains, revocation) which is operationally complex.
- JWKS caching creates a window where a compromised key remains trusted until the cache TTL expires.
Implementation notes
Authorization Code + PKCE
PKCE (Proof Key for Code Exchange, RFC 7636) is mandatory for all public clients (SPAs, mobile apps) that cannot store a client secret. Generate a 32-byte cryptographically random code_verifier, compute code_challenge = BASE64URL(SHA256(code_verifier)), and send the challenge in the authorisation request. The auth server stores the challenge; when the app exchanges the code for tokens, it sends the verifier, which the server hashes and compares to the stored challenge. Without PKCE, an auth code stolen in transit (by a malicious app registered for the same redirect URI) can be exchanged for tokens.
JWT validation checklist
Validate these claims on every request: iss (expected issuer URL), aud (must include your API's identifier), exp (must be in the future), nbf (not before, if present), and the signature (using the matching JWKS public key). Fetch JWKS at startup and cache; on a kid mismatch (new key), refresh the JWKS once. Never use the none algorithm — reject tokens with "alg": "none".
Token introspection vs local JWT validation
Local validation uses the JWKS public key to verify the signature cryptographically without a network call — fast, scalable, but relies on expiry for revocation. Token introspection calls the auth server's /introspect endpoint on every request to check real-time token status — supports immediate revocation but adds latency (10–50ms) and a dependency on the auth server's availability. Strategy: use local validation for standard API calls; use introspection for high-value operations (money transfers, account deletion) or immediately after a revocation event is received via webhook.
Service-to-service with mTLS
In a zero-trust network, every service presents a client certificate (SPIFFE/SVID format in service mesh environments). The server verifies the client certificate against the trusted CA. The service's identity is encoded in the certificate's Subject Alternative Name (SAN). Istio and Linkerd automate certificate issuance and rotation via the SPIRE or Cert Manager integration. mTLS ensures that even if the network is compromised, an attacker without a valid certificate cannot impersonate a service.
API key management
API keys should be: generated as cryptographically random values (32+ bytes, base64 or hex encoded); stored hashed (SHA-256) server-side — never store plaintext API keys; scoped to an explicit set of allowed operations; associated with a named consumer identity for attribution; rotatable without service disruption (dual-key validity period during rotation); and auditable — every use logged with timestamp, key ID, and endpoint.
Common failure modes
- Long-lived access tokens with no refresh rotation — a stolen token remains valid for hours or days; use short-lived (15–60 min) access tokens with refresh token rotation.
- Accepting tokens signed with "none" algorithm — attackers craft tokens without a signature; always verify the algorithm against an allowlist.
- Not validating the
audclaim — a valid token issued for service A is accepted by service B; always check that the audience includes your API identifier. - Logging access tokens — tokens in access logs can be stolen from log aggregators; scrub tokens from logs.
- Using the Implicit flow — deprecated; access tokens in URL fragments are stored in browser history; migrate to Authorization Code + PKCE.
- Hardcoded API keys in source code — keys in Git history are effectively public; use secrets managers (AWS Secrets Manager, HashiCorp Vault).
Decision checklist
- Is PKCE enforced for all public OAuth 2.0 clients (SPAs, mobile apps)?
- Are JWT claims validated: iss, aud, exp, nbf, and signature?
- Is the "none" algorithm rejected?
- Are access tokens short-lived (≤ 1 hour) with refresh token rotation?
- Are API keys stored hashed, not plaintext, in the auth database?
- Are tokens scrubbed from application logs and error messages?
- For service-to-service, is mTLS or client credentials used (not API keys)?
- Is there a token revocation mechanism for high-value operations?
Example use cases
- SPA + REST API — React SPA uses Authorization Code + PKCE with Auth0; access tokens are 15-minute JWTs stored in memory (not localStorage); refresh tokens rotate on each use and are stored in httpOnly cookies.
- Microservice mesh — Istio enforces mTLS between all services; service identity is a SPIFFE URI in the X.509 certificate SAN; no per-request auth server lookup needed.
- Third-party developer API — API keys are issued per developer application; keys are stored hashed with HMAC-SHA256; each key carries a plan-tier label enforced at the gateway rate limiter.
- CLI tool / device — GitHub CLI uses the Device Code flow; the user opens a browser and enters a code displayed in the terminal; the CLI polls for the token until the user approves.
Related patterns
- API Authorization — after authentication, RBAC/ABAC determines what the identity can do.
- API Gateway — JWT validation and OAuth enforcement happens at the gateway.
- gRPC — mTLS is the standard authentication mechanism for gRPC service-to-service.
Further reading
- RFC 6749 — The OAuth 2.0 Authorization Framework.
- RFC 7636 — Proof Key for Code Exchange (PKCE).
- RFC 7519 — JSON Web Token (JWT).
- RFC 7662 — OAuth 2.0 Token Introspection.
- OWASP API Security Top 10 — Broken Authentication (API2:2023).
- OAuth 2.0 Security Best Current Practice — draft-ietf-oauth-security-topics.