encode/httpx · error · ProtocolError

Unexpected qop value "{qop!r}" in digest auth

Error message

Unexpected qop value "{qop!r}" in digest auth

What it means

Raised as httpx.ProtocolError by DigestAuth._resolve_qop when the server's qop value is present but contains neither 'auth' nor 'auth-int'. The split list does not match any qop value httpx can negotiate.

Source

Thrown at httpx/_auth.py:340

                if field not in NON_QUOTED_FIELDS
                else NON_QUOTED_TEMPLATE
            )
            header_value += template.format(field, to_str(value))

        return header_value

    def _resolve_qop(self, qop: bytes | None, request: Request) -> bytes | None:
        if qop is None:
            return None
        qops = re.split(b", ?", qop)
        if b"auth" in qops:
            return b"auth"

        if qops == [b"auth-int"]:
            raise NotImplementedError("Digest auth-int support is not yet implemented")

        message = f'Unexpected qop value "{qop!r}" in digest auth'
        raise ProtocolError(message, request=request)


class _DigestAuthChallenge(typing.NamedTuple):
    realm: bytes
    nonce: bytes
    algorithm: str
    opaque: bytes | None
    qop: bytes | None

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Capture the 401 WWW-Authenticate header to confirm the invalid qop value.
  2. Do not use httpx.DigestAuth against that endpoint; use a custom Auth or token.
  3. Fix the server's Digest configuration to advertise qop=auth.

Example fix

// before
client.get(url, auth=httpx.DigestAuth("u", "p"))
// after
def auth_flow(req):
    req.headers["Authorization"] = "Bearer <token>"
    yield req
client.get(url, auth=auth_flow)
Defensive patterns

Strategy: try-catch

Validate before calling

import re
probe = client.get(url)
for h in (probe.headers.get_list("www-authenticate") if probe.status_code == 401 else []):
    if h.lower().startswith("digest "):
        m = re.search(r'qop="?([^"]+)"?', h)
        if m:
            qops = [q.strip() for q in m.group(1).split(",")]
            negotiable = "auth" in qops or qops == ["auth-int"]
            # if not negotiable, DigestAuth will raise ProtocolError

Type guard

def digest_qop_negotiable(www_auth_values: list[str]) -> bool:
    import re
    for h in www_auth_values:
        if h.lower().startswith("digest "):
            m = re.search(r'qop="?([^"]+)"?', h)
            if m:
                qops = [q.strip() for q in m.group(1).split(",")]
                return "auth" in qops or qops == ["auth-int"]
    return True

Try / catch

try:
    resp = client.get(url, auth=httpx.DigestAuth(user, pw))
except httpx.ProtocolError as exc:
    log.warning("Unrecognized digest qop: %s", exc)
    resp = client.get(url, headers={"Authorization": "Bearer <token>"})

Prevention

When it happens

Trigger: A 401 Digest challenge with an unrecognized qop token, e.g. 'Digest ... qop="foobar"' or a malformed 'qop=""'. _resolve_qop finds no 'auth' and the list is not exactly ['auth-int'].

Common situations: Non-compliant servers with typos in qop; proprietary qop extensions; corrupted or truncated WWW-Authenticate headers.

Related errors


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