spring-projects/spring-security · error · BadCredentialsException

DigestAuthenticationFilter.missingAuth

DigestAuthenticationFilter.missingAuth

Error message

Missing mandatory digest value; received header {0}

What it means

Same 'missing mandatory digest value' message but for the RFC 2617 'auth' qop path: when qop=auth is declared, the header must also contain nc (nonce count) and cnonce (client nonce). validateAndDecode throws this BadCredentialsException if either is null.

Solutions

  1. Have the client include nc (e.g. 00000001) and a random cnonce whenever qop is present in the challenge response.
  2. If the client only supports RFC 2069, configure/choose a server qop that matches, or fall back to no-qop digest, ensuring both sides agree.
  3. Regenerate the digest response including cnonce and nc in the MD5 input (A2 includes qop, nc, cnonce for RFC 2617) so the header is self-consistent.
  4. Log the full header from the exception message and diff each parameter against the RFC 2617 grammar.

Example fix

// before
Authorization: Digest username="u", realm="r", nonce="n", uri="/", qop="auth", response="d"
// after
Authorization: Digest username="u", realm="r", nonce="n", uri="/", qop="auth", nc="00000001", cnonce="0a4f113b", response="d"
Defensive patterns

Strategy: validation

Validate before calling

Map<String,String> p = parseDigestParams(header);
if ("auth".equals(p.get("qop")) && (p.get("nc") == null || p.get("cnonce") == null)) {
    throw new IllegalStateException("qop=auth requires nc and cnonce in the Digest header");
}

Try / catch

try {
    chain.doFilter(request, response);
} catch (BadCredentialsException e) {
    if (e.getMessage().contains("Missing mandatory digest value")) {
        response.sendError(401, "qop=auth requires nc and cnonce");
    }
}

Prevention

When it happens

Trigger: A Digest Authorization header sets qop="auth" but omits nc and/or cnonce. This happens when a client advertises a qop it doesn't fully implement, or builds the header from an incomplete parameter set.

Common situations: Clients that parse the server's qop options but never generate the client nonce/counter; header built by string concatenation missing nc/cnonce; upgraded servers now requiring qop=auth while clients were written for legacy RFC 2069 (no qop).

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/fa8e115598ed8256. Report an issue: GitHub.

Appendix: source

Thrown at web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java:368

			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}'"));
			}
			// Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint)
			final byte[] nonceBytes;
			try {
				nonceBytes = Base64.getDecoder().decode(this.nonce.getBytes());
			}
			catch (IllegalArgumentException ex) {
				throw new BadCredentialsException(
						DigestAuthenticationFilter.this.messages.getMessage("DigestAuthenticationFilter.nonceEncoding",

View on GitHub (pinned to 96852e8860)