HTTP Authentication and Authorization Headers

LESSON

HTTP Protocol and Content Delivery

011 25 min intermediate

HTTP Authentication and Authorization Headers

The core idea: Authorization moves explicit identity proof on each request, but the server still has to validate the credential, decide what it permits, and expose failures with headers and status codes clients can act on.

Core Insight

Imagine the same shop system, but now the caller is a mobile app rather than a browser page. The app calls https://api.shop.test/orders/842 from a phone on a cafe network. There is no browser cookie jar automatically attaching a session cookie. There is no same-origin policy protecting response exposure. The app needs to prove something about the caller directly in the HTTP request.

The common shape is an Authorization header:

GET /orders/842 HTTP/1.1
Host: api.shop.test
Authorization: Bearer eyJhbGciOi...
Accept: application/json

That header is not magic. It is a field that carries credentials. The server, gateway, or authorization layer must decide whether the credential is well formed, still valid, intended for this API, issued by a trusted authority, and strong enough for the action being requested. Only after that does the application decide whether this authenticated caller may read order 842.

This lesson's main correction is that authentication and authorization are related but not identical. Authentication answers "who or what is making this request?" Authorization answers "is that principal allowed to do this specific thing?" A valid token can still be forbidden from a resource. An expired token can say nothing useful about the current request. A token with the wrong audience may be valid somewhere else and useless here.

The trade-off is stateless API access versus revocation and leakage risk. Putting credentials in request headers works well for mobile clients, CLIs, service-to-service calls, and APIs that do not want cookie transport. It also means every hop that logs, forwards, caches, retries, or debugs requests must treat that header as sensitive request state.

What the Headers Are For

HTTP authentication uses a small set of visible protocol pieces:

Authorization      -> client sends credentials to the origin server
WWW-Authenticate   -> server describes how to authenticate after a 401
Proxy-Authorization -> client sends credentials to an HTTP proxy
Proxy-Authenticate  -> proxy describes how to authenticate after a 407

Most application APIs deal mainly with Authorization and WWW-Authenticate. The other pair matters when an HTTP proxy, not the origin application, is demanding credentials.

The value inside Authorization begins with a scheme name. Two common examples are:

Authorization: Basic dXNlcjpwYXNz
Authorization: Bearer eyJhbGciOi...

Basic carries a base64-encoded username and password pair. It is simple, but it is only acceptable over TLS and is rarely the right shape for modern public APIs because every request carries reusable password-like material.

Bearer means "whoever presents this token can use it." A bearer token might be an opaque random string that the server looks up, or a structured token such as a JWT that the server can validate locally. The HTTP layer does not make it safe by itself. The safety comes from TLS in transit, short lifetimes, careful storage on the client, token validation on the server, and narrow permissions.

The server can challenge a missing or invalid credential with 401 Unauthorized and a WWW-Authenticate header:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="shop-api"
Cache-Control: no-store

Despite the name, 401 Unauthorized is primarily about authentication: the request has not supplied acceptable credentials. The challenge tells a client what kind of credential could make a retry meaningful.

If the credential is valid but not allowed to perform the action, the usual response is 403 Forbidden:

HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-store

{"error":"order_not_accessible"}

This distinction helps clients and operators. 401 suggests the client may need to obtain, refresh, or resend credentials. 403 says credentials were understood, but the authorization decision refused the action.

The Request Path

Trace the mobile app request through a typical API stack:

mobile app
  -> edge gateway
  -> auth middleware or token validator
  -> orders service
  -> database

The mobile app first gets an access token from an identity system. That login or token issuance flow is outside this HTTP lesson, but the result is visible: the app has a token with a lifetime and a set of permissions.

The app sends:

GET /orders/842 HTTP/1.1
Host: api.shop.test
Authorization: Bearer eyJhbGciOi...

The edge gateway may do the first checks. It can reject obviously malformed credentials, enforce TLS-only access, strip sensitive headers from logs, and route only valid-looking requests deeper into the system. In many architectures, the gateway validates the token signature or calls an introspection endpoint. In others, the application service performs validation itself.

The validator asks several concrete questions:

Is the scheme allowed?          Bearer, not an unexpected scheme
Is the token authentic?         signature, lookup, or introspection succeeds
Is it still valid?              exp and not-before checks pass
Was it meant for this API?      audience matches shop-api
Who is the principal?           subject, client id, or service identity
What can it do?                 scopes, roles, claims, or policy facts

Only after that does resource authorization begin. The orders service checks whether the principal is allowed to read order 842. A customer token might include sub=user_123 and scope=orders:read. The database row might say owner_user_id=user_456. The token is valid, but the resource does not belong to that user. The correct outcome is not "token invalid." It is "forbidden."

The important trace is:

credential present
-> credential validated
-> principal extracted
-> requested action named
-> resource ownership or policy checked
-> response status explains the failing gate

When teams skip one of those steps, failures become vague. Every denied request turns into 401. Operators cannot tell expired tokens from missing scopes. Clients retry with the same bad token. Security reviews cannot identify which component is authoritative for identity and which one owns resource policy.

Bearer Tokens: Local Validation or Introspection

Bearer tokens are popular because they make request authentication explicit and portable. They also force a design choice: should the API validate the token locally, or ask an authorization server about it?

With local validation, the token carries enough information for the API to verify it. A signed JWT is the common example. The API checks the signature using a trusted key, verifies issuer, audience, expiration, and other claims, then extracts identity and permissions.

The advantage is speed and resilience. The orders API does not need to call the identity service on every request. That reduces latency and removes a runtime dependency from the hot path. The cost is revocation. If a token is valid for 30 minutes, and the user loses access after 5 minutes, the API may keep accepting the token until it expires unless there is an additional revocation mechanism.

With introspection, the API or gateway sends the token to an authorization server and asks whether it is active and what it means. The advantage is central control. Revocation and account disablement can take effect quickly. The cost is a dependency on the introspection service. If that service is slow or unavailable, the API must decide whether to fail closed, use a cache, or degrade in some narrow way.

Neither shape is universally better. A public mobile API often uses short-lived access tokens and refresh tokens to balance performance with revocation. Service-to-service systems may prefer local validation with short lifetimes and strong key rotation. High-risk operations may add a fresh policy check even when the token validates locally.

The operational question is not "JWT or opaque token?" It is:

How long can a bad or outdated credential remain useful,
and which component is allowed to answer that question?

Worked Path: Reading and Updating an Order

Start with a successful read:

GET /orders/842 HTTP/1.1
Host: api.shop.test
Authorization: Bearer token_for_user_123
Accept: application/json

The gateway validates the token:

issuer: https://id.shop.test
audience: shop-api
subject: user_123
scope: orders:read
expires: 10 minutes from now

The orders service loads order 842:

order id: 842
owner_user_id: user_123
state: processing

The token is valid, the scope allows reads, and the resource belongs to the subject. The service returns:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store

{"id":"842","state":"processing"}

Now try an update with the same token:

PATCH /orders/842 HTTP/1.1
Host: api.shop.test
Authorization: Bearer token_for_user_123
Content-Type: application/json

{"shipping_address":"new address"}

The token validates, but it only has orders:read, not orders:write. The response should identify the authorization failure without leaking sensitive token material:

HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-store

{"error":"insufficient_scope"}

If the token were expired instead, the better response would be a challenge:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="shop-api", error="invalid_token"
Cache-Control: no-store

The difference matters for client behavior. An expired token can trigger a refresh flow. A missing write scope should not be fixed by blindly retrying the same request. A resource ownership failure may need a user-facing "not found" or "not allowed" response depending on the product's information disclosure policy.

What Changes from Cookies and CORS

With cookies, the browser attaches matching credentials automatically. That is convenient for browser sessions, but it creates ambient authority and CSRF concerns. With Authorization, the client application usually has to place the credential explicitly on the request. That makes it a better fit for mobile apps, CLIs, and service clients.

With CORS, the browser decides whether JavaScript may read a cross-origin response. That rule still applies to browser clients even if they use Authorization: Bearer. A browser-based single-page app may need both a valid Authorization header and a valid CORS policy that allows the frontend origin to send and read that request.

The boundary sentence is: Authorization helps the server decide who is calling; CORS helps the browser decide whether script may see the response; neither one replaces resource-level authorization.

Operational Failure Modes

Failure: logging credentials. Authorization is sensitive. Reverse proxies, application logs, error reporters, analytics tools, and trace attributes should redact or omit it. A copied bearer token is often enough to act as the user until it expires.

Failure: forwarding identity ambiguously. If a gateway validates a token and forwards X-User-Id or similar headers to an upstream service, the service must trust only the gateway path and must strip client-supplied versions of those headers at the edge. Otherwise a caller can forge identity by sending the internal header directly.

Failure: using 401 for every denial. Expired, missing, malformed, insufficient scope, and wrong-resource cases need different handling. Use 401 when authentication can be supplied or refreshed. Use 403 when the credential is understood but the action is not allowed.

Failure: caching private authorized responses. Requests with Authorization often produce user-specific data. Shared caches should not reuse those responses unless the cache policy is deliberately designed for it. Cache-Control: private or no-store is common for account data.

Failure: treating scopes as the whole policy. Scopes such as orders:read say what class of action a token may request. They do not automatically prove ownership of a specific order. Resource policy still has to check the actual object, tenant, organization, account state, or relationship.

Useful signals include rates of 401 versus 403, token validation latency, introspection failures, expired-token spikes after deployments, missing-scope errors by client version, and redaction tests in logs and traces. A good auth header design is observable without exposing the credential itself.

Connections

This lesson completes the browser credential boundary started by cookies and CORS. Cookies explain automatic credential transport. CORS explains browser response exposure. Authorization explains explicit credential transport for clients that should carry proof on each request.

The next lesson shifts from request identity to body transfer. That matters because authenticated APIs often carry large uploads, downloads, compressed bodies, and streaming responses. The same rule continues: identity and authorization decisions happen at the boundary before the service spends expensive work on a body it should not accept or reveal.

Close the lesson and classify five failures from memory: missing token, expired token, valid token with wrong scope, valid token for the wrong order, and browser CORS block after a valid token. For each one, name the likely status code or browser symptom and the component that should make the decision.

Resources

Key Takeaways

PREVIOUS CORS, Origins, and Browser Enforcement NEXT Request and Response Bodies: Streaming, Compression, and Range Delivery