HTTP Digest Authentication

The challenge-response scheme that tried to fix Basic before HTTPS was universal — and why you should not choose it today.

Digest (RFC 7616) exists to answer one question: how do you prove you know a password without sending it? Its answer is a challenge-response handshake.

The exchange

  1. The server replies 401 with a nonce — a one-time random value — plus a realm and the hashing algorithm.
  2. The client computes HA1 = H(username:realm:password) and HA2 = H(method:uri).
  3. It sends response = H(HA1:nonce:nc:cnonce:qop:HA2).
  4. The server recomputes the same value from its stored HA1. Equal means the client knew the password.

The password itself never crosses the wire, and the nonce makes a captured response useless for a later request — that was genuinely clever in 1999.

Why it lost

  • The server must store a password-equivalent. To verify, it needs HA1, which is not a slow salted hash. Anyone who steals your user table can authenticate directly.
  • MD5 by default. SHA-256 is allowed by RFC 7616 but implementations lag badly.
  • HTTPS made it pointless. TLS already prevents interception, which was Digest's entire reason to exist.
  • The same browser problems as Basic: no logout, no MFA, no styling.

What replaced the idea

The "prove knowledge without sending the secret" concept did not die — it moved. HMAC request signing does it for APIs, and WebAuthn does it for users with a key pair instead of a shared secret. Both are strictly better.

Example

Example · http
# 1. Server challenges
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm="api",
                  qop="auth",
                  nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
                  opaque="5ccc069c403ebaf9f0171e9517f40e41",
                  algorithm=SHA-256

# 2. Client answers — no password on the wire
GET /api/me HTTP/1.1
Authorization: Digest username="alice",
               realm="api",
               nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
               uri="/api/me",
               qop=auth,
               nc=00000001,
               cnonce="0a4f113b",
               response="6629fae49393a05397450978507c4ef1",
               opaque="5ccc069c403ebaf9f0171e9517f40e41"

When to use it

  • An old IP camera or router management API only offers Digest, so a monitoring script must implement it to poll the device.
  • A migration project keeps a Digest endpoint alive for two legacy clients while new clients move to bearer tokens on a parallel route.
  • A security audit flags Digest because verification requires storing HA1, meaning a database leak is immediately usable for login.

More examples

Computing the response by hand

The nonce count and client nonce are what stop replay: reusing a captured response with a different nc fails, and the server tracks which counts it has seen.

Example · javascript
import { createHash } from 'crypto';
const H = (s) => createHash('sha256').update(s).digest('hex');

const ha1 = H(`alice:api:s3cret`);          // username:realm:password
const ha2 = H(`GET:/api/me`);               // method:uri

const response = H([
  ha1,
  'dcd98b7102dd2f0e8b11d0f600bfb0c093',     // server nonce
  '00000001',                               // nonce count
  '0a4f113b',                               // client nonce
  'auth',                                   // qop
  ha2,
].join(':'));

// The server stores ha1 and recomputes the same chain.
// Note what that means: ha1 IS the credential. Steal it, log in.

curl speaks it, so you can test legacy devices

Digest always costs an extra round-trip on the first request, since the client cannot compute a response until it has the server's nonce.

Example · bash
# --digest makes curl perform the 401 → challenge → retry dance for you
curl --digest -u admin:admin123 https://camera.local/api/status

# Watch the two round-trips
curl -v --digest -u admin:admin123 https://camera.local/api/status 2>&1 \
  | grep -E '^(> GET|< HTTP|< WWW-Authenticate|> Authorization)'

# Compare with Basic, which is a single request
curl -v -u admin:admin123 https://camera.local/api/status

Discussion

  • Be the first to comment on this lesson.