HTTP Authentication
How credentials are passed in HTTP requests - the Authorization header and its schemes
The Authorization header
HTTP has a built-in mechanism for passing credentials: the Authorization request header. Its format is always:
Authorization: <scheme> <credentials>
The scheme tells the server how to interpret what follows. The most common schemes are Basic and Bearer. The server reads the scheme first and then parses the credentials accordingly.
When a server requires authentication and the client hasn't provided it, it responds with 401 Unauthorized and a WWW-Authenticate header telling the client which scheme it expects:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="api" # or for Basic auth: WWW-Authenticate: Basic realm="Admin area"
The realmis just a label describing the protected area - it's shown to the user in Basic auth browser dialogs but is otherwise informational.
Basic auth
The oldest scheme. The client takes username:password, base64-encodes it, and sends it in the header on every request:
# "user:password" base64-encoded = "dXNlcjpwYXNzd29yZA==" Authorization: Basic dXNlcjpwYXNzd29yZA==
Base64 is not encryption- it's encoding. Anyone who can read the header can decode it instantly. Basic auth is only safe over HTTPS, which ensures the header is encrypted in transit. Over plain HTTP it's equivalent to sending your password in plaintext.
Because credentials are sent on every request, there's no token to expire or revoke - to change access you change the password. This is a limitation.
When Basic auth is acceptable: internal tools, simple scripts calling your own APIs, or service-to-service calls within a trusted network where simplicity matters more than fine-grained control. Most modern public APIs have moved away from it.
Bearer tokens
The scheme used by OAuth 2.0 and JWT. "Bearer" means whoever holds this token is granted access - like cash, possession is the claim. No username, no password:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The token was issued by an authentication server after credentials were verified. It carries identity and permissions inside it (if it's a JWT) or the server looks them up from a database (if it's an opaque token). Either way, the raw credentials never travel over the wire again after login.
Advantages over Basic auth: tokens can expire, be revoked, be scoped to specific permissions, and be issued to third parties without sharing the underlying credentials.
The risk: a leaked bearer token grants access immediately to whoever has it, with no further verification. Keep them out of URLs, logs, and client-side storage. HTTPS is non-negotiable.
API keys
API keys are not an official HTTP auth scheme - they're a convention. Different APIs put them in different places:
# As a Bearer token (most common for modern APIs) Authorization: Bearer sk_live_abc123 # As a custom header (OpenAI, Stripe use this pattern too) X-API-Key: sk_live_abc123 # As a query parameter (avoid - ends up in logs and browser history) GET /api/data?api_key=sk_live_abc123
API keys are typically long-lived credentials issued to developers or services, not end users. They identify the calling application, not a person. A key might have scopes (read-only vs read-write) and can be revoked independently without changing any passwords.
Never put API keys in query parameters in production - they'll appear in server logs, browser history, and referrer headers. Header is always preferable.
Comparing the schemes
In practice
- Calling a third-party API (Stripe, OpenAI, GitHub) - almost always
Authorization: Bearer <api-key>. - OAuth flow -
Authorization: Bearer <access-token>on every API call after the token exchange. - Your own Django REST API - DRF's
TokenAuthenticationusesAuthorization: Token <token>(a custom scheme);JWTAuthenticationusesBearer. - Quick internal scripts - Basic auth is fine if it's over HTTPS and you control both ends.
- Never query params for secrets in production.