encode/httpx · error · ProtocolError

Malformed Digest WWW-Authenticate header

Error message

Malformed Digest WWW-Authenticate header

What it means

Raised as httpx.ProtocolError when DigestAuth._parse_challenge cannot find a required field. After a 401 with a 'WWW-Authenticate: Digest ...' header, httpx splits the header into key=value pairs and reads 'realm' and 'nonce' (KeyError on either triggers this). It indicates the server sent a Digest challenge missing RFC 7616 mandatory fields.

Source

Thrown at httpx/_auth.py:253

        assert scheme.lower() == "digest"

        header_dict: dict[str, str] = {}
        for field in parse_http_list(fields):
            key, value = field.strip().split("=", 1)
            header_dict[key] = unquote(value)

        try:
            realm = header_dict["realm"].encode()
            nonce = header_dict["nonce"].encode()
            algorithm = header_dict.get("algorithm", "MD5")
            opaque = header_dict["opaque"].encode() if "opaque" in header_dict else None
            qop = header_dict["qop"].encode() if "qop" in header_dict else None
            return _DigestAuthChallenge(
                realm=realm, nonce=nonce, algorithm=algorithm, opaque=opaque, qop=qop
            )
        except KeyError as exc:
            message = "Malformed Digest WWW-Authenticate header"
            raise ProtocolError(message, request=request) from exc

    def _build_auth_header(
        self, request: Request, challenge: _DigestAuthChallenge
    ) -> str:
        hash_func = self._ALGORITHM_TO_HASH_FUNCTION[challenge.algorithm.upper()]

        def digest(data: bytes) -> bytes:
            return hash_func(data).hexdigest().encode()

        A1 = b":".join((self._username, challenge.realm, self._password))

        path = request.url.raw_path
        A2 = b":".join((request.method.encode(), path))
        # TODO: implement auth-int
        HA2 = digest(A2)

        nc_value = b"%08x" % self._nonce_count
        cnonce = self._get_client_nonce(self._nonce_count, challenge.nonce)

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Inspect the raw 401 response's WWW-Authenticate header (send once with follow_redirects=False / without auth) to see which fields are missing.
  2. Do not use httpx.DigestAuth against that endpoint; switch to BasicAuth, a bearer token, or a custom Auth subclass.
  3. Fix or reconfigure the server/proxy so its Digest challenge includes at least realm and nonce.

Example fix

// before
client.get(url, auth=httpx.DigestAuth("u", "p"))
// after
client.get(url, headers={"Authorization": "Bearer <token>"})
Defensive patterns

Strategy: try-catch

Validate before calling

import httpx
# Probe the endpoint once to inspect the challenge before relying on DigestAuth
probe = client.get(url)
if probe.status_code == 401:
    wa = probe.headers.get_list("www-authenticate")
    has_digest = any(h.lower().startswith("digest ") for h in wa)
    field_keys = set()
    for h in wa:
        if h.lower().startswith("digest "):
            for part in h.split(None, 1)[1].split(","):
                k = part.split("=", 1)[0].strip()
                field_keys.add(k.lower())
    digest_ok = has_digest and {"realm", "nonce"} <= field_keys
    # if not digest_ok, do not attempt DigestAuth

Type guard

def has_required_digest_fields(www_auth_values: list[str]) -> bool:
    for h in www_auth_values:
        if h.lower().startswith("digest "):
            fields = {}
            for part in h.split(None, 1)[1].split(","):
                k, _, v = part.partition("=")
                fields[k.strip().lower()] = v.strip().strip('"')
            return "realm" in fields and "nonce" in fields
    return False

Try / catch

try:
    resp = client.get(url, auth=httpx.DigestAuth(user, pw))
except httpx.ProtocolError as exc:
    # Server returned a malformed Digest challenge
    log.warning("Digest challenge malformed: %s", exc)
    resp = client.get(url, headers={"Authorization": "Bearer <token>"})

Prevention

When it happens

Trigger: Calling client.get(url, auth=httpx.DigestAuth(user, pw)) against a server whose 401 'WWW-Authenticate: Digest' header omits 'realm' or 'nonce' (e.g. a header like 'Digest opaque="x"' with no realm/nonce).

Common situations: Non-compliant or custom auth servers/proxies that issue a Digest challenge without all mandatory fields; servers returning a 'stale' indicator without realm; misconfigured reverse proxies.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/fdff53ce036779d1.json. Report an issue: GitHub.