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
- The server replies
401with a nonce — a one-time random value — plus a realm and the hashing algorithm. - The client computes
HA1 = H(username:realm:password)andHA2 = H(method:uri). - It sends
response = H(HA1:nonce:nc:cnonce:qop:HA2). - 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
# 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.
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.
# --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