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

  1. 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.
  2. 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.
  3. Check proxies/gateways (and header sanitizers) aren't dropping or rewriting Authorization parameters.
  4. 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

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


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)