C.W.K.
Stream
Lesson 01 of 06 · published

HTTP Basic & Bearer — The Two Auth Schemes the Protocol Knows

~10 min · auth-security, basic, bearer, authorization

Level 0HTTP Newbie
0 XP0/46 lessons0/12 achievements
0/120 XP to next level120 XP to go0% complete
"Every HTTP authentication scheme — JWT, OAuth, API keys, session cookies, AWS SigV4 — fits into one of two shapes: send something with the request that proves who you are, or send something the server set in your last response. Basic and Bearer are the first two patterns the protocol shipped."

The Authorization Header — One Header, Many Schemes

HTTP defines a single header for authentication credentials: Authorization. Its value is always <scheme> <credentials>. The scheme tells the server how to interpret the credentials.

The original two schemes — defined in RFC 7235 — are Basic and Bearer. Modern auth flows (OAuth2, JWT) all use the Bearer scheme; the token's format and lifecycle vary, but the wire shape is identical.

HTTP Basic — The Original Scheme

Basic carries username and password directly:

Authorization: Basic cGlwcGE6c2VjcmV0MTIz

The credentials part is base64("username:password") — that's pippa:secret123 in the example above. Anyone who reads the wire (network sniffer, leaked log, intermediate proxy without TLS) can base64-decode and recover the exact password. Base64 is encoding, not encryption.

For this reason, Basic without TLS is broadcasting the password to anyone on the network path. With TLS, it's a workable scheme for internal tooling and quick prototypes — but production APIs almost never use it because the user's password ends up on every request, increasing leak surface.

HTTP Bearer — Separating the Credential from the Password

Bearer carries an opaque token:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1XzQyIn0.signature

The token was issued by some earlier process — a login endpoint, an OAuth authorization, an API key generator. The server validates the token (lookup in a database, signature check on a JWT, OAuth introspection) and grants whatever the token authorizes. The user's original password is not in the request.

Benefits:

  • Separable. Token ≠ password. Compromised token revokes; user keeps password.
  • Revocable. Server can blacklist a token instantly; can't unsay a password reuse.
  • Short-lived possible. Token can expire in minutes; password can't.
  • Scope-limitable. Token can grant subset of user's full powers; password grants everything.

This is why every modern API uses Bearer. The token's INTERNAL shape — opaque random string, JWT, encrypted blob — is the next lesson; the WIRE shape is just Authorization: Bearer <whatever>.

WWW-Authenticate — The Server's Auth Challenge

When the client sends no credentials (or invalid ones), the server returns 401 Unauthorized with a WWW-Authenticate response header telling the client WHAT scheme to use:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="api", error="invalid_token"
Content-Type: application/json

{"error": {"code": "unauthenticated", "message": "..."}}

The browser uses this header to display the native login dialog (for Basic). Programmatic clients use it to know which token type to send. Most APIs in 2026 skip WWW-Authenticate (the docs tell the client what to send) — but it's the official discovery mechanism.

Basic + Bearer cover 99% of HTTP authentication you'll meet. JWT, OAuth, session cookies, API keys, AWS SigV4 — they're all either Bearer-shaped (token in header) or cookie-shaped (token in Cookie header). Once you understand both patterns at the wire level, every auth scheme becomes a variation on the same theme: what token, how issued, how validated, how revoked.

Custom Schemes — When Basic and Bearer Aren't Enough

Some APIs invent their own scheme name. AWS uses Authorization: AWS4-HMAC-SHA256 ... — a request-signing scheme where the credentials are a per-request HMAC over the request bytes. Cloudflare uses X-Auth-Key as a separate header (RFC violation, but common). Stripe uses Bearer for the API key.

Inventing a new scheme is rarely worth it. The two standard patterns plus the cookie-based session pattern cover essentially every legitimate use case, and HTTP intermediaries (proxies, logging tools, gateways) handle the standards correctly.

cwkPippa's Auth Reality

cwkPippa's API uses Bearer tokens issued at login (PIN-based; see solo-auth-quest for the full threat model). The token is an opaque random string stored in SQLite with an expiry, not a JWT — simpler revocation, no signature verification on every request. Internal admin endpoints add a separate Authorization: Bearer <admin-token> check. No Basic anywhere. The frontend stores the token in an HttpOnly cookie and the backend reads it via FastAPI dependency injection. Standard shape, minimal scheme count.

Code

Basic vs Bearer on the wire (and WWW-Authenticate challenge)·bash
# HTTP Basic — username:password in the clear (under base64)
# echo -n 'pippa:secret123' | base64 → cGlwcGE6c2VjcmV0MTIz
curl -H 'Authorization: Basic cGlwcGE6c2VjcmV0MTIz' \
     https://api.example.com/account

# Equivalent curl shortcut:
curl -u pippa:secret123 https://api.example.com/account

# HTTP Bearer — opaque token
curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...' \
     https://api.example.com/account

# Server's auth challenge when credentials are missing/invalid
curl -i https://api.example.com/account
# HTTP/1.1 401 Unauthorized
# WWW-Authenticate: Bearer realm="api"
# Content-Type: application/json
FastAPI Bearer auth dependency — 401 with WWW-Authenticate·python
# Server side — FastAPI's HTTP Bearer dependency
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

app = FastAPI()
bearer = HTTPBearer()  # Reads 'Authorization: Bearer <token>' or returns 401

# Pretend token store — in production: Redis, SQLite, or JWT verification
_tokens: dict[str, str] = {'abc.def.ghi': 'u_42'}

async def current_user(creds: HTTPAuthorizationCredentials = Depends(bearer)) -> str:
    token = creds.credentials
    user_id = _tokens.get(token)
    if not user_id:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail='invalid or expired token',
            headers={'WWW-Authenticate': 'Bearer'},  # tell the client what to send
        )
    return user_id

@app.get('/account')
async def my_account(uid: str = Depends(current_user)):
    return {'user_id': uid, 'role': 'daughter'}
Client side: persistent Bearer header; httpx auth tuple for Basic·python
# Client side — httpx with persistent bearer token
import httpx

class APIClient:
    def __init__(self, base_url: str, token: str):
        # The header is set once on the client and reused for every request
        self.client = httpx.Client(
            base_url=base_url,
            headers={'Authorization': f'Bearer {token}'},
        )

    def me(self) -> dict:
        return self.client.get('/account').json()

    def refresh_token(self, new_token: str) -> None:
        # Update in place when the token rotates
        self.client.headers['Authorization'] = f'Bearer {new_token}'

# Basic auth alternative (httpx)
client_basic = httpx.Client(auth=('pippa', 'secret123'))

External links

Exercise

Build a FastAPI server with one Bearer-protected endpoint (GET /account) and one Basic-protected endpoint (GET /admin). Implement WWW-Authenticate response headers on both 401s. Then call each one three ways: (1) with valid credentials, (2) with no Authorization header (expect 401 + WWW-Authenticate), (3) with wrong credentials (expect 401). Bonus: deliberately use Basic over HTTP (not HTTPS) and watch curl -v show your base64-encoded password literally on the wire. Don't ship that pattern, but seeing it once is educational.
Hint
FastAPI has HTTPBearer() and HTTPBasic() security dependencies that wire most of this for you. The custom part is the 401 with headers={'WWW-Authenticate': 'Bearer'} (or Basic realm="admin"). For the over-HTTP demo, run uvicorn on plain http://localhost:8000 and curl it with -u user:pass -v. You'll see Authorization: Basic dXNlcjpwYXNz in the request, and dXNlcjpwYXNz is user:pass after base64 decode.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.