64. HTTP authentication and authorization on the wire
64.1 The mechanisms
| Scheme | Wire format | Notes |
|---|---|---|
| Basic | Authorization: Basic base64(user:pass) |
Not encryption — base64 is reversible. HTTPS only. Fine for internal/service use. |
| Digest | Challenge-response with nonces | Avoids sending the password, but weak by modern standards and rarely used |
| Bearer | Authorization: Bearer <token> |
The dominant API pattern (OAuth 2.0, RFC 6750) |
| API key | Usually a custom header (X-Api-Key) or query param |
Query params leak into logs, Referer, and browser history — use a header |
| mTLS | Client certificate at the TLS layer (Ch 38.10) | Strongest; identity established before HTTP starts |
| Cookie session | Cookie: (Ch 60) |
Browser-native; brings CSRF concerns |
| HMAC request signing | Signature over method + path + body + timestamp (AWS SigV4 style) | Resists replay and tampering; no bearer token to steal |
64.2 The 401 challenge flow
Client: GET /private
Server: 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token",
error_description="expired"
Client: GET /private
Authorization: Bearer eyJ...
Server: 200 OK
401 vs 403: 401 = "I don't know who you are (or your credentials are
bad) — authenticate and retry." 403 = "I know who you are and you may not do
this — retrying won't help." Sending 401 for an authorization failure causes
clients to loop through token refresh pointlessly.
407 Proxy Authentication Required is the same flow for a proxy, using
Proxy-Authenticate / Proxy-Authorization.
64.3 OAuth 2.0 / OIDC at the wire level
You don't need the whole spec, but you should recognise the shapes:
Authorization Code + PKCE (the correct flow for web and mobile apps)
1. browser -> /authorize?response_type=code&client_id=..&redirect_uri=..
&scope=..&state=..&code_challenge=..&code_challenge_method=S256
2. user authenticates at the identity provider
3. redirect back with ?code=..&state=.. <- state defends against CSRF
4. server-side: POST /token with code + code_verifier -> access_token,
refresh_token, id_token
5. call the API with Authorization: Bearer <access_token>
Client Credentials (machine-to-machine)
POST /token grant_type=client_credentials -> access_token
Operational notes:
- Access tokens should be short-lived (minutes); refresh tokens long-lived and rotated on use.
- JWTs are not encrypted — they're signed. Anyone can read the payload
(
base64urldecode it). Never put secrets in one. - Validate: signature,
iss,aud,exp,nbf, and algorithm (rejectalg: noneand algorithm confusion). - Revocation is the JWT weakness — a stateless token is valid until it expires. Keep lifetimes short, or maintain a denylist.
64.4 Real-world example
An internal API used API keys passed as ?api_key=.... The keys turned up in: the
CDN's access logs, the load balancer's logs, the Referer header sent to a
third-party analytics script on an error page, and a screenshot in a support
ticket. Rotating them took a week. Moving to Authorization: Bearer plus
short-lived tokens removed four of those exposure paths immediately.
Try it yourself
curl -sI https://httpbin.org/basic-auth/user/pass | head -3 # 401 + WWW-Authenticate
curl -s -u user:pass https://httpbin.org/basic-auth/user/pass # 200
# decode a JWT payload (it is NOT encrypted)
echo '<jwt>' | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | python3 -m json.tool