aio-libs/aiohttp · error · ClientError

Security issue: Digest auth challenge contains empty 'nonce'

Error message

Security issue: Digest auth challenge contains empty 'nonce' value

What it means

Raised as a ClientError when a server's HTTP Digest auth challenge returns a 'nonce' parameter that is present but empty (e.g. `nonce=""`). The nonce is the security-critical value used to prevent replay attacks in RFC 7616; an empty nonce makes the digest response trivially replayable, so aiohttp refuses to proceed rather than silently produce a weak credential. Unlike a missing realm/nonce (a malformed challenge), this is flagged specifically as a security issue.

Source

Thrown at aiohttp/client_middleware_digest_auth.py:258

        """
        challenge = self._challenge
        if "realm" not in challenge:
            raise ClientError(
                "Malformed Digest auth challenge: Missing 'realm' parameter"
            )

        if "nonce" not in challenge:
            raise ClientError(
                "Malformed Digest auth challenge: Missing 'nonce' parameter"
            )

        # Empty realm values are allowed per RFC 7616 (SHOULD, not MUST, contain host name)
        realm = challenge["realm"]
        nonce = challenge["nonce"]

        # Empty nonce values are not allowed as they are security-critical for replay protection
        if not nonce:
            raise ClientError(
                "Security issue: Digest auth challenge contains empty 'nonce' value"
            )

        qop_raw = challenge.get("qop", "")
        # Preserve original algorithm case for response while using uppercase for processing
        algorithm_original = challenge.get("algorithm", "MD5")
        algorithm = algorithm_original.upper()
        opaque = challenge.get("opaque", "")

        # 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

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Report the bug to the server/proxy operator: their Digest challenge is producing an empty nonce, which is a server-side security defect.
  2. If you cannot fix the server, disable digest auth for this endpoint and use a different auth mechanism (e.g. bearer token, basic auth over TLS).
  3. Verify with curl --digest to confirm the server itself is the source of the empty nonce.
  4. Do not attempt to patch the nonce client-side; an empty nonce defeats replay protection.

Example fix

// before: server returns WWW-Authenticate: Digest realm="x", nonce="", qop="auth"
resp = await session.get(url, auth=DigestAuth(login, pass))

// after: fix server nonce generation, or switch auth
resp = await session.get(url, headers={"Authorization": "Bearer <token>"})
Defensive patterns

Strategy: try-catch

Try / catch

from aiohttp import ClientError
try:
    await session.get(url, auth=DigestAuth(login, pwd))
except ClientError as e:
    if "empty 'nonce'" in str(e):
        # server bug; fall back to a different auth method
        ...

Prevention

When it happens

Trigger: The error fires in the digest-auth middleware during challenge parsing (line 257-260), after 'nonce' is confirmed present (line 247) but `not nonce` evaluates True (line 257). It triggers on any 401 response whose WWW-Authenticate digest header contains `nonce=""`.

Common situations: Hitting a misconfigured reverse proxy, load balancer, or custom auth filter that emits a Digest challenge with an empty nonce; old/buggy server-side nonce generators; man-in-the-middle appliances that strip nonce values.

Related errors


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