How HTTP Works

Request header

> Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

Authorization Header: Bearer, Basic and 401 Fixes

Authorization header carries credentials as scheme plus token. Bearer and Basic formats, WWW-Authenticate, why proxies strip it, redirects, CORS and storage.

Reviewed 3 min readintermediate4 sourcesTry itMarkdown
Direction
Request
Category
Authentication
Spec
RFC 9110 §11.6.2
JS can set it
Yes
CORS-safelisted
No: may trigger a preflight
On this page

TL;DR: Authorization: <scheme> <credentials> proves who is calling. Use Bearer <token> for APIs and Basic base64(user:pass) only over HTTPS; if the server says 401, check the WWW-Authenticate response header for the scheme it wants.

Format

GET /api/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.abc123

A missing or rejected credential gets a challenge that names the scheme:

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

RFC 9110 section 11.6.2 defines the header; schemes are registered with IANA. Common ones: Basic (RFC 7617), Bearer (RFC 6750), Digest (rare), Negotiate (Kerberos/NTLM), AWS4-HMAC-SHA256 (SigV4), and DPoP (RFC 9449, sender-constrained tokens). “API key” is not a scheme; people send keys as Bearer, or in X-API-Key.

Bearer

Whoever holds the token can use it, which is why lifetime and transport matter:

curl -H "Authorization: Bearer $TOKEN" https://api.example.com/me

The 401 WWW-Authenticate error values come from RFC 6750: invalid_request (400), invalid_token (401), insufficient_scope (403). Expired tokens, wrong audience, clock skew on JWT exp or nbf, and a missing Bearer prefix are the usual reasons for invalid_token.

Basic

base64("user:pass"), sent on every request:

curl -u alice:s3cret https://example.com/admin -v
# > Authorization: Basic YWxpY2U6czNjcmV0
echo YWxpY2U6czNjcmV0 | base64 -d

base64 is encoding, not encryption. Browsers show a native login prompt when a 401 carries WWW-Authenticate: Basic realm="Admin". Basic is fine for internal tools behind TLS, bad for anything else. Compare passwords with a constant-time function and rate-limit attempts.

Why the header goes missing

  • Cross-origin redirect. Per the Fetch Standard, browsers strip Authorization when a redirect leaves the origin; curl does the same unless you pass --location-trusted. A 301 from http:// to https:// or from example.com to www.example.com drops it.
  • Proxy or FastCGI. Apache with mod_fcgid/PHP-FPM may not pass it; add SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 or RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]. nginx forwards it by default with proxy_pass, but proxy_set_header Authorization "" or a custom fastcgi_param HTTP_AUTHORIZATION $http_authorization; is needed for FastCGI. AWS API Gateway authorizers and some CDNs may replace it.
  • CORS. Authorization is not a safelisted header, so a cross-origin call triggers a preflight, and the response needs Access-Control-Allow-Headers: authorization. A wildcard * in that header does not cover Authorization; list it explicitly. See Access-Control-Allow-Headers.
  • Caching. A shared cache will not store a response to a request with Authorization unless the response has public, s-maxage or must-revalidate (RFC 9111 section 3.5). Add Vary: Authorization if you intentionally cache.

401 or 403

401 means the server did not accept who you are, so send credentials or fresh ones. 403 means it knows who you are and refuses. See 401 vs 403, 401 and 403.

Token storage in browsers

A token in localStorage sent as Bearer is immune to CSRF, but any XSS can read and exfiltrate it. An HttpOnly; Secure; SameSite=Lax cookie cannot be read by script but is attached automatically, so you need CSRF defences for state-changing requests. For a first-party SPA the cookie (or a backend-for-frontend that holds tokens server-side) is usually the safer choice. For mobile apps and third-party API clients, use Authorization: Bearer with short-lived access tokens (minutes) and rotating refresh tokens. See Cookie security and Authentication.

Security checklist

  • Only over HTTPS; add Strict-Transport-Security.
  • Redact Authorization and Cookie in access logs, APM, and error reporters.
  • Never put credentials in the URL; they leak via Referer, history and logs.
  • Pin the verification algorithm for JWTs and validate aud, iss, exp.
  • Send Cache-Control: no-store on token endpoints.

In practice

Express.js
// Middleware to verify a Bearer token
function requireAuth(req, res, next) {
  const auth = req.headers.authorization
  if (!auth || !/^Bearer /i.test(auth)) {
    return res
      .status(401)
      .set('WWW-Authenticate', 'Bearer realm="api"')
      .json({ error: 'Missing token' })
  }
  try {
    // Pin the algorithm: never let the token choose it
    req.user = jwt.verify(auth.slice(7), process.env.JWT_SECRET, { algorithms: ['HS256'] })
    next()
  } catch {
    res
      .status(401)
      .set('WWW-Authenticate', 'Bearer error="invalid_token"')
      .json({ error: 'Invalid token' })
  }
}

app.get('/api/profile', requireAuth, (req, res) => res.json(req.user))
Next.js App Router
// middleware.ts: protect API routes globally
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth'

export async function middleware(request: NextRequest) {
  const token = request.headers.get('authorization')?.replace(/^Bearer /i, '')
  if (!token) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }
  const payload = await verifyToken(token)
  if (!payload) {
    return NextResponse.json({ error: 'Invalid token' }, { status: 401 })
  }
  return NextResponse.next()
}

export const config = { matcher: '/api/:path*' }
Django
# Django REST Framework
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
import jwt

class BearerAuthentication(BaseAuthentication):
    def authenticate_header(self, request):
        return 'Bearer realm="api"'  # makes DRF send 401 + WWW-Authenticate

    def authenticate(self, request):
        auth = request.META.get('HTTP_AUTHORIZATION', '')
        if not auth.startswith('Bearer '):
            return None
        try:
            payload = jwt.decode(auth[7:], settings.SECRET_KEY, algorithms=['HS256'])
            return (User.objects.get(id=payload['user_id']), auth[7:])
        except Exception:
            raise AuthenticationFailed('Invalid token')

Frequently asked questions

What is the Authorization header?

It is a request header that carries credentials for the target server in the form "Authorization: <scheme> <credentials>", for example Bearer followed by an access token or Basic followed by base64 of user:password. A server that rejects it answers 401 with a WWW-Authenticate challenge.

What is the difference between Bearer and Basic authentication?

Basic sends base64(username:password) on every request, which is trivially decoded, so it needs HTTPS and a dedicated credential. Bearer sends an opaque or signed token that grants access to whoever holds it, so it also needs HTTPS and short lifetimes. Neither is inherently more secure; the token can be scoped and revoked, which a password usually cannot.

Why is my Authorization header missing on the server?

Common causes: a redirect to a different origin (fetch and curl drop it), a proxy or Apache/CGI configuration that does not pass it through, a CORS preflight that did not list authorization in Access-Control-Allow-Headers, or the client sending the token in another header. Check the raw request at the first server that sees it.

Should I use the Authorization header or cookies?

Use Authorization for APIs consumed by non-browser clients and third parties. For a browser app that calls only its own backend, an HttpOnly, Secure, SameSite cookie keeps the token out of reach of XSS, at the cost of needing CSRF protection.

Is the Authorization header secure?

Only over TLS. On plain HTTP it is readable by anyone on the path. Even over HTTPS it can leak through access logs, error reports and proxies, so redact it from logs and never put credentials in URLs.

Is Authorization case sensitive?

The header name is not, and HTTP/2 and HTTP/3 lowercase it. The scheme name is case-insensitive by RFC 9110, but many servers wrongly compare "Bearer " exactly, so send it as Bearer.

Sources

  1. MDN Web Docs: Authorizationdeveloper.mozilla.org
  2. RFC 9110 section 11.6.2: Authorizationrfc-editor.org
  3. RFC 6750: The OAuth 2.0 Bearer Token Usagerfc-editor.org
  4. RFC 7617: The Basic HTTP Authentication Schemerfc-editor.org

Keep going

Browse /search