aio-libs/aiohttp · error · ClientError

Digest auth error: Unsupported Quality of Protection (qop) v

Error message

Digest auth error: Unsupported Quality of Protection (qop) value(s): {qop_raw}

What it means

Raised as a ClientError when a Digest auth challenge includes a 'qop' (Quality of Protection) parameter whose token list contains no value aiohttp can negotiate. aiohttp only implements 'auth' and 'auth-int' (RFC 2617/7616); if the server advertises only unsupported tokens (or unparseable garbage), the client cannot compute the response digest correctly and refuses rather than send an unauthenticated-looking request.

Source

Thrown at aiohttp/client_middleware_digest_auth.py:285

        # Convert string values to bytes once
        nonce_bytes = nonce.encode("utf-8")
        realm_bytes = realm.encode("utf-8")
        # Use the encoded request-target (raw_path_qs) since that is what is
        # transmitted on the wire and what the server signs against. Using the
        # decoded form would cause digest verification to fail when the path
        # or query string contains percent-encoded reserved characters.
        path = URL(url).raw_path_qs

        # Process QoP
        qop = ""
        qop_bytes = b""
        if qop_raw:
            valid_qops = {"auth", "auth-int"}.intersection(
                {q.strip() for q in qop_raw.split(",") if q.strip()}
            )
            if not valid_qops:
                raise ClientError(
                    f"Digest auth error: Unsupported Quality of Protection (qop) value(s): {qop_raw}"
                )

            qop = "auth-int" if "auth-int" in valid_qops else "auth"
            qop_bytes = qop.encode("utf-8")

        if algorithm not in DigestFunctions:
            raise ClientError(
                f"Digest auth error: Unsupported hash algorithm: {algorithm}. "
                f"Supported algorithms: {', '.join(SUPPORTED_ALGORITHMS)}"
            )
        hash_fn: Final = DigestFunctions[algorithm]

        def H(x: bytes) -> bytes:
            """RFC 7616 Section 3: Hash function H(data) = hex(hash(data))."""
            return hash_fn(x).hexdigest().encode()

        def KD(s: bytes, d: bytes) -> bytes:

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Reconfigure the server to advertise qop="auth" (most widely supported) instead of auth-conf or custom values.
  2. If the server only supports auth-conf, use a different HTTP client that implements it, or tunnel via TLS and disable qop server-side.
  3. Confirm the exact WWW-Authenticate header the server emits and verify qop parsing.

Example fix

# server config (nginx example) - before:
# auth_basic_user_file ... ; (digest module emitting qop=auth-conf)
# after: configure digest module to qop=auth
Defensive patterns

Strategy: try-catch

Try / catch

from aiohttp import ClientError
try:
    resp = await session.get(url, auth=DigestAuth(login, pwd))
except ClientError as e:
    if "Unsupported Quality of Protection" in str(e):
        # negotiate a non-digest auth path or report server misconfig

Prevention

When it happens

Trigger: Fires at line 284-287 after intersecting the server's qop token set with {"auth","auth-int"} yields an empty set. Common when the server sends qop values like "auth-conf", proprietary tokens, or malformed comma-separated lists.

Common situations: Server configured for qop=auth-conf (confidentiality, which aiohttp doesn't implement); legacy Microsoft IIS or custom DAV servers using non-standard qop tokens; typos in server config.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/3dd888deb00e2af6.json. Report an issue: GitHub.