spring-projects/spring-security · error · BadCredentialsException
DigestAuthenticationFilter.missingMandatory
DigestAuthenticationFilter.missingMandatory
Error message
Missing mandatory digest value; received header {0} What it means
DigestAuthenticationFilter's DigestAuthHeaderDetails.validateAndDecode throws this when the Digest Authorization header is missing one of the mandatory parameters required by RFC 2069: username, realm, nonce, uri, or response. The full received header is embedded in the message ({0} = section212response) to aid debugging.
Solutions
- Compare the received header (printed in the exception message) against the full RFC 2069 parameter set and have the client include username, realm, nonce, uri, and response.
- If the client is doing challenge/response, make it first consume the WWW-Authenticate challenge from the 401 and echo back all realm/nonce values it received.
- Check proxies/gateways (and header sanitizers) aren't dropping or rewriting Authorization parameters.
- If Digest auth isn't actually intended, switch the client to the scheme the server entry point advertises (or configure the server for Basic/Form auth instead).
Example fix
// before (manual client header) Authorization: Digest username="admin" // after Authorization: Digest username="admin", realm="Realm", nonce="<from WWW-Authenticate>", uri="/api", response="<md5 digest>"
Defensive patterns
Strategy: validation
Validate before calling
Set<String> required = Set.of("username", "realm", "nonce", "uri", "response");
Set<String> present = parseDigestParams(authorizationHeader).keySet();
if (!present.containsAll(required)) throw new IllegalStateException("Digest header missing: " + required);
Try / catch
try {
chain.doFilter(request, response);
} catch (BadCredentialsException e) {
if (e.getMessage().startsWith("Missing mandatory digest value")) {
response.sendError(401, "Digest header incomplete: " + e.getMessage());
}
} Prevention
- Consume the WWW-Authenticate 401 challenge and echo every parameter (realm, nonce) back
- Use a proven digest-auth client library rather than hand-assembling the header
- Verify proxies don't strip Authorization header parameters
- Unit-test your digest header against the RFC 2069 parameter list
When it happens
Trigger: A request carries a Digest 'Authorization' header that omits any of username, realm, nonce, uri, or response — e.g. a client sends only 'Digest username="x"' or a partially-implemented digest client drops the nonce or response field.
Common situations: Hand-rolled or non-conformant HTTP clients; custom auth middleware that forwards a stripped-down header; proxies removing header parameters; misconfigured front-ends sending Basic-style headers while the server expects Digest; test harnesses constructing the header manually and forgetting fields.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- DigestAuthenticationFilter.incorrectRealm
- DigestAuthenticationFilter.missingAuth
- DigestAuthenticationFilter.nonceNotTwoTokens
- Authentication for password change failed.
- Authentication.getCredentials() cannot be null
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/6972867e047044e8.
Report an issue: GitHub.
Appendix: source
Thrown at web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java:360
this.username = headerMap.get("username");
this.realm = headerMap.get("realm");
this.nonce = headerMap.get("nonce");
this.uri = headerMap.get("uri");
this.response = headerMap.get("response");
this.qop = headerMap.get("qop"); // RFC 2617 extension
this.nc = headerMap.get("nc"); // RFC 2617 extension
this.cnonce = headerMap.get("cnonce"); // RFC 2617 extension
logger.debug(
LogMessage.format("Extracted username: '%s'; realm: '%s'; nonce: '%s'; uri: '%s'; response: '%s'",
this.username, this.realm, this.nonce, this.uri, this.response));
}
void validateAndDecode(@Nullable String entryPointKey, @Nullable String expectedRealm)
throws BadCredentialsException {
// Check all required parameters were supplied (ie RFC 2069)
if ((this.username == null) || (this.realm == null) || (this.nonce == null) || (this.uri == null)
|| (this.response == null)) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(
"DigestAuthenticationFilter.missingMandatory", new Object[] { this.section212response },
"Missing mandatory digest value; received header {0}"));
}
// Check all required parameters for an "auth" qop were supplied (ie RFC 2617)
if ("auth".equals(this.qop)) {
if ((this.nc == null) || (this.cnonce == null)) {
logger.debug(LogMessage.format("extracted nc: '%s'; cnonce: '%s'", this.nc, this.cnonce));
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(
"DigestAuthenticationFilter.missingAuth", new Object[] { this.section212response },
"Missing mandatory digest value; received header {0}"));
}
}
// Check realm name equals what we expected
if (!this.realm.equals(expectedRealm)) {
throw new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(
"DigestAuthenticationFilter.incorrectRealm", new Object[] { this.realm, expectedRealm },
"Response realm name '{0}' does not match system realm name of '{1}'"));
}View on GitHub (pinned to 96852e8860)