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 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.
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.
Example fix
# before
mw = DigestAuthMiddleware(login='u', password='p')
await session.get('https://srv/protected') # challenge = 'Digest realm="x"' (no nonce)
# after — verify and fix server header to include nonce
# expected: WWW-Authenticate: Digest realm="x", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093" Defensive patterns
Strategy: try-catch
Validate before calling
async def challenge_has_nonce(session, url) -> bool:
resp = await session.get(url)
wa = resp.headers.get('WWW-Authenticate', '')
await resp.release()
return 'nonce=' in wa.lower() Type guard
def challenge_valid(challenge_header: str) -> bool:
low = challenge_header.lower()
return low.startswith('digest') and 'realm=' in low and 'nonce=' in low Try / catch
from aiohttp import ClientError
try:
resp = await session.get(url)
except ClientError as e:
if "Missing 'nonce'" in str(e):
# server challenge is incomplete; cannot authenticate
handle_bad_challenge(e)
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Digest auth error: Unsupported hash algorithm
- Digest auth error: Unsupported Quality of Protection (qop)…
- Malformed Digest auth challenge: Missing 'realm' parameter
- Security issue: Digest auth challenge contains empty…
- A ":" is not allowed in username (RFC 1945#section-11.1)
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/511f23d191ead1fd.
Report an issue: GitHub.
Appendix: 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 d041d4d0fd)