API Authentication Guide 2026
Practitioner's reference for API authentication: JWT vs server-side sessions, OAuth 2.1 flows, API key rotation patterns, mutual TLS, and the attacks (alg=none, key confusion, replay) that secure code defends against. Written for backend engineers picking auth for new services or hardening existing ones.
Table of contents
- Picking an auth scheme — decision matrix
- JWT vs server-side sessions
- JWT — structure, algorithms, and the attacks
- OAuth 2.1 — the four flows you need
- API keys — rotation and scoping
- Mutual TLS — service-to-service auth
- Production patterns — refresh tokens, idempotency, audit
- Common security mistakes
- Authoritative references
1. Picking an auth scheme — decision matrix
Every API auth decision is a tradeoff between simplicity, scale, and trust boundaries. The shortest path:
| Scenario | Use | Why |
|---|---|---|
| Single-org, server + browser SPA, single backend | HTTP-only session cookie + CSRF token | Simplest, server-controlled revocation, no token leak via XSS. |
| Mobile app + backend (single org) | JWT access token + opaque refresh token | Bearer token works on mobile; refresh allows rotation. |
| Third-party developers calling your API | OAuth 2.1 with PKCE | Standard delegated authorization; user controls scope. |
| Internal service-to-service | mTLS, or scoped service JWT | Mutual cryptographic identity; no shared secrets. |
| Public read API with rate limiting | API key in Authorization header | Simple, identifies caller for quota. |
| Microservices with central identity provider | OIDC (OAuth + OpenID Connect) ID tokens | Standard, vendor-neutral. |
| Edge / IoT with intermittent connectivity | Long-lived signed tokens with crypto attestation | Offline operation; verify on reconnect. |
2. JWT vs server-side sessions
The single most-debated choice. Each has clear tradeoffs:
| Aspect | Server-side session | JWT |
|---|---|---|
| Storage | Server (Redis, DB, memory) | Client (cookie, localStorage, header) |
| Revocation | Instant (delete from store) | Token lifetime ± blacklist required |
| Scaling | Needs shared session store | Stateless verification |
| Network round-trip | None (just verify signature/cookie) | None (just verify signature) |
| CSRF protection | Required (CSRF token + SameSite) | Not needed if not in cookie |
| XSS exposure | None (HttpOnly cookie) | Risk if stored in localStorage |
| Cross-origin / mobile | Awkward | Native fit |
| Token size | ~30 byte cookie | 500 byte – 4 KB header |
The 2026 consensus among security engineers:
- Browser SPA + same-origin backend: use HttpOnly session cookies. Avoids the XSS-localStorage problem.
- Mobile apps, third-party APIs, multi-domain: use JWTs (or OAuth-issued JWTs).
- Hybrid: short-lived JWT access token (5–15 min) + opaque refresh token in a server-side store. Best of both — stateless during the access window, revocable via refresh-token deletion.
3. JWT — structure, algorithms, and the attacks
A JSON Web Token (RFC 7519) is three Base64URL-encoded parts joined by dots: header.payload.signature. The payload is signed but not encrypted — anyone with the token can read its contents.
Algorithm choice
| Algorithm | Type | Key | When to use |
|---|---|---|---|
| HS256 | HMAC + SHA-256 | Shared secret (≥ 32 bytes) | Single service signs and verifies its own tokens. |
| RS256 | RSA + SHA-256 | 2048-bit RSA private key | Issuer signs; multiple consumers verify with public key. |
| ES256 | ECDSA P-256 + SHA-256 | 256-bit ECC key | Same as RS256 with smaller signatures, faster on mobile. |
| EdDSA / Ed25519 | Edwards-curve DSA | 256-bit Ed25519 key | Modern default for new asymmetric setups (RFC 8037). |
| none | Unsigned | None | Never accept. Spec allows it; safe libraries reject by default. |
The classic JWT attacks — and defenses
- alg: none. Attacker resigns the token with
{"alg": "none"}and an empty signature. Vulnerable libraries accept it because the spec technically allows it. Defense: hard-code an allow-list of algorithms; reject "none" explicitly. - Algorithm confusion (RS256 → HS256). Server is configured for RS256 but accepts any algorithm. Attacker takes the public key (which is, well, public) and uses it as the HMAC secret with HS256. The signature now verifies because the library treats the public key as a shared secret. Defense: tie the algorithm to the key type — never let HS algorithms verify with an RSA public key.
- Key confusion via
jku/jwkheaders. Attacker sets a header pointing the server at an attacker-controlled JWKS URL. Defense: never trustjku/jwk/x5u/kidvalues from the token; resolve keys from a server-side allow-list. - Long-lived tokens. Leaked tokens give permanent access if they never expire. Defense: short-lived access (5–60 min) + refresh tokens.
- Replay across services. Token signed for service A is replayed against service B (which trusts the same issuer). Defense: enforce
aud(audience) claim validation. - Information leakage. Sensitive PII in payload because "it's signed". JWTs are not encrypted. Defense: keep payload minimal; use JWE (encrypted JWT) for confidential data.
- Stale clock skew.
expvalidation fails because client and server clocks drift. Defense: allow ±30–60 seconds of clock skew tolerance in verification.
Verification done right (Node.js example)
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'], // explicit allow-list — defends alg=none
issuer: 'https://issuer.example.com',
audience: 'my-service',
clockTolerance: 60 // ±60s for clock skew
});
4. OAuth 2.1 — the four flows you need
OAuth 2.1 (RFC 9700, draft as of 2026) consolidates the OAuth 2.0 best practices that emerged over a decade. It deprecates the implicit flow, requires PKCE for all public clients, and tightens redirect-URI matching.
Authorization Code Flow with PKCE
The default for most cases. Used by mobile apps, SPAs, server-rendered apps. PKCE (Proof Key for Code Exchange) prevents authorization-code interception attacks.
1. Client generates a random code_verifier (43-128 chars).
2. Client computes code_challenge = SHA256(code_verifier), Base64URL.
3. Client redirects user to /authorize with code_challenge.
4. User authenticates, authorizes scope.
5. Authorization server returns code to redirect_uri.
6. Client exchanges code + code_verifier for access token at /token.
7. Authorization server verifies SHA256(code_verifier) == code_challenge.
8. Returns access_token + refresh_token.
Client Credentials Flow
For machine-to-machine where there is no user — service A calling service B. Client sends its own credentials directly to /token and gets a token scoped to its identity.
Device Authorization Flow
For devices without browsers — TVs, IoT, CLI tools. Server returns a code + URL; user opens URL on a phone, types the code, approves; original device polls until token issued.
Refresh Token Flow
Access tokens are short-lived (5–60 min). When they expire, the client exchanges its refresh token for a new access token. Implement refresh token rotation: each refresh issues a new refresh token; the old one becomes invalid. Detects token theft when a stolen refresh is used after the legitimate one.
OpenID Connect (OIDC)
OIDC layers identity on top of OAuth. The authorization endpoint returns an id_token (JWT) alongside the access token, identifying who the user is. Use OIDC when you need authentication, not just authorization. Most identity providers (Auth0, Okta, Azure AD, Google) speak OIDC.
5. API keys — rotation and scoping
API keys are simpler than OAuth. They identify and authenticate the caller in one credential. Use them for:
- Server-to-server calls within a single organization.
- Public read APIs that need rate limiting per caller.
- Webhooks where the caller authenticates by including a shared secret.
The patterns that make API keys safe:
- Always over HTTPS. Keys in HTTP requests are visible to network observers.
- Send in
Authorizationheader, not URL parameters. URL params leak into logs, browser history, Referer headers. - Scope keys narrowly. Read-only vs read-write, per-resource, per-environment. Stripe's restricted keys are a model.
- Rotate regularly. Every 90 days minimum. Shorter for high-value keys.
- Support multiple active keys per principal. Lets you rotate without downtime.
- Hash keys at rest. Treat API keys like passwords — store SHA-256 of the key, not the key itself.
- Log key usage. Audit access patterns; alert on anomalies (geographic, traffic spike, new IP).
- Generate keys cryptographically. Use the Password Generator for high-entropy keys, or
crypto.randomBytes(32). Pair with a prefix likesk_live_for visibility. - Use a hash to identify keys without exposing them. Store first 8 chars + hash; show
sk_live_a1b2c3...in dashboards.
Use the Hash Generator to verify key hashes match expected values during debugging. Generate cryptographically random keys with the Password Generator set to 32 characters with full charset.
6. Mutual TLS — service-to-service auth
mTLS (RFC 8705 for OAuth) extends the normal TLS handshake with client certificates. The server verifies the client's certificate against a trusted CA before accepting the connection. The result: cryptographic proof of client identity at the connection level, no shared secrets, no tokens to leak.
When mTLS makes sense:
- Internal microservices in a zero-trust network.
- High-value B2B integrations (payments, healthcare).
- Service mesh setups (Istio, Linkerd, Consul Connect handle mTLS automatically).
- Compliance regimes that require strong mutual authentication.
The challenge: certificate distribution and rotation. Most teams that adopt mTLS use a service mesh (Istio, Linkerd) or a private CA (HashiCorp Vault, AWS Private CA, cert-manager on Kubernetes) to automate cert lifecycle.
7. Production patterns — refresh tokens, idempotency, audit
Refresh token rotation
Pattern that catches token theft:
- Every refresh-token use issues a new refresh token AND invalidates the old one.
- If anyone uses the old refresh token after rotation, treat as theft signal — invalidate the entire token family.
- Store refresh tokens with a "family ID" so you can revoke all derivatives if one is compromised.
Idempotency keys
For state-changing operations (POST), include an Idempotency-Key header (UUIDv4 or v7). Server stores the response keyed by this value. Retries with the same key return the cached response instead of double-charging. Stripe, Square, Adyen, Paddle, and modern payments APIs support this. Generate idempotency keys with the UUID Generator.
Audit logging
For every authenticated request, log:
- Timestamp (use the Unix Timestamp Converter for log analysis).
- Caller identity (subject claim, API key prefix, mTLS DN).
- Action and resource.
- Success/failure.
- Source IP, user agent.
- Correlation ID (UUID for distributed trace).
Rate limiting
Per-key, per-IP, per-user. Return 429 Too Many Requests with Retry-After header. Use a sliding-window or token-bucket algorithm; fixed-window has burst issues.
8. Common security mistakes
- Storing JWTs in localStorage. Vulnerable to XSS. Use HttpOnly cookies for browsers; Authorization header for non-browser clients.
- Verifying JWT with
jwt.verify(token, secret)only. Without algorithm allow-list, this opens alg=none. Always passalgorithms: [...]. - Treating JWT logout as "delete the cookie". A copy of the token may exist in browser memory, in monitoring, in a logger. Real logout requires server-side blacklist or short expiry + refresh rotation.
- Hard-coding secrets in client code. Anyone with the secret can forge tokens. Use env vars on the server only.
- Using HS256 with a 12-character secret. Minimum security level requires the secret to be at least as long as the hash output (32 bytes for HS256).
- Skipping issuer/audience validation. Just calling
jwt.verifywithout checkingissandaudopens cross-service replay. - Accepting OAuth tokens without checking the issuer. Validate the JWKS URL against your allow-list.
- Putting passwords in the JWT payload. Even hashed. Use the subject claim and look up via DB instead.
- API keys in URL parameters. Logged everywhere. Always use Authorization header.
- Forgetting to rotate keys after employee departures. Treat API keys like passwords — rotate when someone with access leaves.
- OAuth client secret in mobile/SPA code. Public clients cannot keep secrets. Use PKCE and treat them as such.
- Trusting the
kidheader for key lookup. Resolve from a server-side map; never construct file paths or URLs from token-controlled values.
9. Authoritative references
Auth tools referenced in this guide
Browser-based, never sends data to a server.