The Authorization Header and Friends

Where credentials actually travel, why the query string is the wrong place, and how a server advertises what it accepts.

SyntaxAuthorization: Bearer <token>

HTTP reserves one header for credentials: Authorization. Its value is a scheme followed by the credential.

Authorization: <scheme> <credential>

The scheme name tells the server how to read what follows.

SchemeLooks likeUsed by
BasicBasic dXNlcjpwYXNzbase64 of user:password
BearerBearer eyJhbGci...JWTs, OAuth access tokens
DigestDigest username=..., response=...legacy challenge/response
AWS4-HMAC-SHA256Credential=..., Signature=...request signing

Why not the query string?

Because URLs are written down everywhere: server access logs, CDN logs, browser history, the Referer header sent to third parties, bug-report screenshots, and analytics. A token in ?api_key=... ends up in all of them. A token in a header ends up in none of them by default.

The challenge: WWW-Authenticate

When a server rejects a request for lack of credentials it should say what it wanted:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token",
                  error_description="The access token expired"

This is what makes a generic HTTP client able to react correctly — and it is what makes a browser pop up the native username/password box for Basic.

Cookies are the other channel

Cookies are not sent in Authorization; they get their own Cookie header, and crucially the browser attaches them automatically. That single difference is the source of nearly every trade-off in this course: automatic attachment is convenient, and it is exactly what makes CSRF possible.

Custom headers

X-API-Key: ... is common and perfectly workable. Be aware it is a non-simple header for CORS, so a cross-origin browser request carrying it triggers a preflight — the same as Authorization does.

Example

Example · bash
# Bearer token — the modern default
curl https://dfg.com/api/me -H "Authorization: Bearer $TOKEN"

# Basic — curl builds the base64 for you
curl https://dfg.com/api/me -u alice:s3cret
# sends: Authorization: Basic YWxpY2U6czNjcmV0

# Custom API key header
curl https://dfg.com/api/me -H "X-API-Key: $API_KEY"

# Cookie — normally the browser does this for you
curl https://dfg.com/api/me -H "Cookie: sid=8f2b..."

# ❌ Do not do this: it lands in every access log on the path
curl "https://dfg.com/api/me?api_key=$API_KEY"

When to use it

  • An SDK reads the WWW-Authenticate header on a 401 to decide between refreshing the token and prompting for a fresh login.
  • A security review finds API keys in a year of nginx access logs because an old client passed them as query parameters.
  • A logging middleware redacts Authorization and Cookie headers so that support engineers can read production traces without seeing live credentials.

More examples

Parsing the header safely

Splitting on every colon is a classic bug: it truncates any password containing one. Split on the first colon only.

Example · javascript
function readBearer(req) {
  const header = req.get('authorization');
  if (!header) return null;

  // Scheme is case-insensitive per RFC 7235; the space is a single SP.
  const [scheme, ...rest] = header.split(' ');
  if (scheme.toLowerCase() !== 'bearer') return null;

  const token = rest.join(' ').trim();
  return token.length ? token : null;
}

// Basic is base64 of "user:password" — decoding is not decrypting.
function readBasic(req) {
  const header = req.get('authorization') || '';
  if (!/^basic /i.test(header)) return null;
  const decoded = Buffer.from(header.slice(6), 'base64').toString('utf8');
  const i = decoded.indexOf(':');            // password may contain ':'
  return i < 0 ? null : { user: decoded.slice(0, i), pass: decoded.slice(i + 1) };
}

Redacting credentials in logs

Redact by allowlist-of-shape rather than by hand at each call site — one forgotten logger is enough to spill tokens into a log aggregator you do not control.

Example · javascript
const SENSITIVE = new Set(['authorization', 'cookie', 'set-cookie', 'x-api-key']);

function safeHeaders(headers) {
  return Object.fromEntries(
    Object.entries(headers).map(([k, v]) =>
      [k, SENSITIVE.has(k.toLowerCase()) ? '[redacted]' : v])
  );
}

app.use((req, res, next) => {
  logger.info({ method: req.method, url: req.originalUrl,
                headers: safeHeaders(req.headers) });
  next();
});

Discussion

  • Be the first to comment on this lesson.