{"record":{"id":"511f23d191ead1fd","repo":"aio-libs/aiohttp","slug":"malformed-digest-auth-challenge-missing-nonce-p","errorCode":null,"errorMessage":"Malformed Digest auth challenge: Missing 'nonce' parameter","messagePattern":"Malformed Digest auth challenge: Missing 'nonce' parameter","errorType":"exception","errorClass":"ClientError","httpStatus":null,"severity":"error","filePath":"aiohttp/client_middleware_digest_auth.py","lineNumber":248,"sourceCode":"            url: The request URL\n            body: The request body (used for qop=auth-int)\n\n        Returns:\n            A fully formatted Digest authorization header string\n\n        Raises:\n            ClientError: If the challenge is missing required parameters or\n                         contains unsupported values\n\n        \"\"\"\n        challenge = self._challenge\n        if \"realm\" not in challenge:\n            raise ClientError(\n                \"Malformed Digest auth challenge: Missing 'realm' parameter\"\n            )\n\n        if \"nonce\" not in challenge:\n            raise ClientError(\n                \"Malformed Digest auth challenge: Missing 'nonce' parameter\"\n            )\n\n        # Empty realm values are allowed per RFC 7616 (SHOULD, not MUST, contain host name)\n        realm = challenge[\"realm\"]\n        nonce = challenge[\"nonce\"]\n\n        # Empty nonce values are not allowed as they are security-critical for replay protection\n        if not nonce:\n            raise ClientError(\n                \"Security issue: Digest auth challenge contains empty 'nonce' value\"\n            )\n\n        qop_raw = challenge.get(\"qop\", \"\")\n        # Preserve original algorithm case for response while using uppercase for processing\n        algorithm_original = challenge.get(\"algorithm\", \"MD5\")\n        algorithm = algorithm_original.upper()\n        opaque = challenge.get(\"opaque\", \"\")","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/client_middleware_digest_auth.py#L230-L266","documentation":"Raised by aiohttp's Digest auth middleware when the server's Digest challenge is missing the 'nonce' parameter. The nonce is a server-issued, single-use value essential to Digest auth's replay protection; without it the digest response cannot be computed. aiohttp raises ClientError from _encode() rather than silently sending an unauthenticated or broken request.","triggerScenarios":"Sending a request through DigestAuthMiddleware against a server whose 'WWW-Authenticate: Digest' header contains a realm but no nonce (e.g. 'WWW-Authenticate: Digest realm=\"x\"'). Fires during the challenge-encoding step after the 401 response triggers a re-authentication attempt.","commonSituations":"Custom or buggy server-side Digest implementations that emit an incomplete challenge; middleware/CDN that rewrites the challenge and drops nonce; testing against a mock server that hardcodes a partial header; protocol confusion (server sends Basic but client expects Digest).","solutions":["Capture the raw WWW-Authenticate header with a plain request to confirm nonce is absent.","Correct the server/proxy to include nonce in its Digest challenge (RFC 7616 requires it).","If the server is third-party and unfixable, remove DigestAuthMiddleware and negotiate a supported scheme (Basic, Bearer).","Check that the challenge is actually Digest and not a Basic challenge being misrouted to the Digest middleware."],"exampleFix":"# before\nmw = DigestAuthMiddleware(login='u', password='p')\nawait session.get('https://srv/protected')  # challenge = 'Digest realm=\"x\"' (no nonce)\n\n# after — verify and fix server header to include nonce\n# expected: WWW-Authenticate: Digest realm=\"x\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\"","handlingStrategy":"try-catch","validationCode":"async def challenge_has_nonce(session, url) -> bool:\n    resp = await session.get(url)\n    wa = resp.headers.get('WWW-Authenticate', '')\n    await resp.release()\n    return 'nonce=' in wa.lower()","typeGuard":"def challenge_valid(challenge_header: str) -> bool:\n    low = challenge_header.lower()\n    return low.startswith('digest') and 'realm=' in low and 'nonce=' in low","tryCatchPattern":"from aiohttp import ClientError\n\ntry:\n    resp = await session.get(url)\nexcept ClientError as e:\n    if \"Missing 'nonce'\" in str(e):\n        # server challenge is incomplete; cannot authenticate\n        handle_bad_challenge(e)\n    raise","preventionTips":["Validate the full WWW-Authenticate header in a pre-flight check before authenticated calls.","Don't mock Digest servers by hand — use a compliant library to generate challenges.","Log the challenge header on auth failure for fast diagnosis."],"tags":["digest-auth","authentication","http","client-middleware"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}