aio-libs/aiohttp · error · ClientError

Malformed Digest auth challenge: Missing 'nonce' parameter

Error message

Malformed Digest auth challenge: Missing 'nonce' parameter

What it means

Raised as `ClientError` in `DigestAuthMiddleware._encode` (client_middleware_digest_auth.py:247-250) when the parsed challenge lacks a `nonce` key. The server's nonce is essential to Digest auth: it's part of H(A1)/H(A2), the client counter (`nc`) is tracked against it, and it's replay-protection material (client_middleware_digest_auth.py:215-216). A separate guard at line 257-260 also rejects an *empty* nonce string for the same security reason.

Source

Thrown at aiohttp/client_middleware_digest_auth.py:248

            url: The request URL
            body: The request body (used for qop=auth-int)

        Returns:
            A fully formatted Digest authorization header string

        Raises:
            ClientError: If the challenge is missing required parameters or
                         contains unsupported values

        """
        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", "")

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Capture and inspect the raw `WWW-Authenticate` header.
  2. Fix the server to include a non-empty `nonce=` in the Digest challenge.
  3. Ensure the challenge is well-formed (proper quoting: `nonce="abc123"`).
  4. Catch `ClientError` and surface a clear auth-failure rather than retrying.

Example fix

// before
# server sends: WWW-Authenticate: Digest realm="r"   (no nonce)
# -> ClientError: Missing 'nonce'
// after (server-side)
# WWW-Authenticate: Digest realm="r", nonce="opaque-random-value", qop="auth"
Defensive patterns

Strategy: try-catch

Validate before calling

from aiohttp import hdrs

def challenge_has_nonce(resp_headers) -> bool:
    auth = resp_headers.get(hdrs.WWW_AUTHENTICATE, '')
    return 'nonce=' in auth.lower()

Try / catch

from aiohttp import ClientError

try:
    resp = await session.get(url, middlewares=[digest_mw])
except ClientError as e:
    if "Missing 'nonce'" in str(e):
        log.error('server Digest challenge lacks nonce; check WWW-Authenticate')
    raise

Prevention

When it happens

Trigger: Server returns `WWW-Authenticate: Digest realm="x"` with no `nonce=`; the parser failed to extract nonce due to quoting/format issues; nonce field is present but empty.

Common situations: Server misconfiguration; buggy auth server omitting nonce; intermediary stripping challenge parameters; a Digest challenge from a server that doesn't fully implement RFC 7616.

Related errors


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