{"record":{"id":"5e6cc1cd33bdb903","repo":"spring-projects/spring-security","slug":"digestauthenticationfilter-noncenotnumeric","errorCode":"DigestAuthenticationFilter.nonceNotNumeric","errorMessage":"Nonce token should have yielded a numeric first token, but was {0}","messagePattern":"Nonce token should have yielded a numeric first token, but was (.+?)","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":401,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java","lineNumber":403,"sourceCode":"\t\t\t\tthrow new BadCredentialsException(\n\t\t\t\t\t\tDigestAuthenticationFilter.this.messages.getMessage(\"DigestAuthenticationFilter.nonceEncoding\",\n\t\t\t\t\t\t\t\tnew Object[] { this.nonce }, \"Nonce is not encoded in Base64; received nonce {0}\"));\n\t\t\t}\n\t\t\t// Decode nonce from Base64 format of nonce is: base64(expirationTime + \":\" +\n\t\t\t// md5Hex(expirationTime + \":\" + key))\n\t\t\tString nonceAsPlainText = new String(nonceBytes);\n\t\t\tString[] nonceTokens = StringUtils.delimitedListToStringArray(nonceAsPlainText, \":\");\n\t\t\tif (nonceTokens.length != 2) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.nonceNotTwoTokens\", new Object[] { nonceAsPlainText },\n\t\t\t\t\t\t\"Nonce should have yielded two tokens but was {0}\"));\n\t\t\t}\n\t\t\t// Extract expiry time from nonce\n\t\t\ttry {\n\t\t\t\tthis.nonceExpiryTime = Long.valueOf(nonceTokens[0]);\n\t\t\t}\n\t\t\tcatch (NumberFormatException nfe) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.nonceNotNumeric\", new Object[] { nonceAsPlainText },\n\t\t\t\t\t\t\"Nonce token should have yielded a numeric first token, but was {0}\"));\n\t\t\t}\n\t\t\t// Check signature of nonce matches this expiry time\n\t\t\tString expectedNonceSignature = DigestAuthUtils.md5Hex(this.nonceExpiryTime + \":\" + entryPointKey);\n\t\t\tif (!Utf8.isEqual(expectedNonceSignature, nonceTokens[1])) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.nonceCompromised\", new Object[] { nonceAsPlainText },\n\t\t\t\t\t\t\"Nonce token compromised {0}\"));\n\t\t\t}\n\t\t}\n\n\t\tString calculateServerDigest(@Nullable String password, String httpMethod) {\n\t\t\t// Compute the expected response-digest (will be in hex form). Don't catch\n\t\t\t// IllegalArgumentException (already checked validity)\n\t\t\treturn DigestAuthUtils.generateDigest(DigestAuthenticationFilter.this.passwordAlreadyEncoded, this.username,\n\t\t\t\t\tthis.realm, password, httpMethod, this.uri, this.qop, this.nonce, this.nc, this.cnonce);\n\t\t}","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java#L385-L421","documentation":"The first token of the decoded nonce must be the expiration time as a numeric string (milliseconds). When Long.valueOf(nonceTokens[0]) fails, validateAndDecode throws this BadCredentialsException with the full nonce plaintext in the message — the nonce content is not in the format produced by DigestAuthenticationEntryPoint.","triggerScenarios":"The decoded nonce's first ':'-separated token is non-numeric, e.g. a nonce generated by a different library or application version whose payload format differs, or a client-fabricated nonce that happens to Base64-decode and split but lacks the numeric expiry prefix.","commonSituations":"Migrating from another digest implementation (different nonce layout) while clients replay old nonces; rolling upgrades where old and new servers issue different nonce formats; custom entry point modifications that changed the payload layout.","solutions":["Clear cached/stale nonces on the client and obtain a fresh nonce from this server's WWW-Authenticate challenge.","Ensure all server instances (load-balanced pool) run the same Spring Security version and identical DigestAuthenticationEntryPoint configuration.","If you customized nonce generation, restore the standard format base64(expiryMillis + \":\" + md5Hex(expiryMillis + \":\" + key)).","Verify no intermediary is rewriting the Authorization header between client and server."],"exampleFix":"// before (custom nonce format)\nString nonce = Base64.getEncoder().encodeToString(UUID.randomUUID().toString().getBytes());\n// after (server-compatible)\nlong expiry = System.currentTimeMillis() + validityMillis;\nString nonce = Base64.getEncoder().encodeToString((expiry + \":\" + DigestAuthUtils.md5Hex(expiry + \":\" + key)).getBytes());","handlingStrategy":"validation","validationCode":"String plain = new String(java.util.Base64.getDecoder().decode(nonce.getBytes(StandardCharsets.UTF_8)));\nString expiry = plain.split(\":\", -1)[0];\nLong.parseLong(expiry); // throws NumberFormatException client-side if not numeric\n","typeGuard":"boolean nonceHasNumericExpiry(String decodedNonce) {\n    String first = decodedNonce.split(\":\", -1)[0];\n    try { Long.parseLong(first); return true; } catch (NumberFormatException e) { return false; }\n}","tryCatchPattern":"try {\n    chain.doFilter(request, response);\n} catch (BadCredentialsException e) {\n    if (e.getMessage().contains(\"numeric first token\")) {\n        response.sendError(401, \"Nonce format from a foreign issuer; request a fresh challenge\");\n    }\n}","preventionTips":["Clear cached nonces after server upgrades or migrations between digest implementations","Pin all load-balanced nodes to the same Spring Security version and entry point config","Do not customize nonce generation away from base64(expiry:md5(expiry:key))","Validate the decoded nonce's first token is a long before sending"],"tags":["spring-security","digest-auth","nonce","format-mismatch"],"backgroundTag":"invalid-argument-format","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}