{"record":{"id":"8daa31c6848231b2","repo":"spring-projects/spring-security","slug":"digestauthenticationfilter-nonceencoding","errorCode":"DigestAuthenticationFilter.nonceEncoding","errorMessage":"Nonce is not encoded in Base64; received nonce {0}","messagePattern":"Nonce is not encoded in Base64; received nonce (.+?)","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":401,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java","lineNumber":385,"sourceCode":"\t\t\t\t\tlogger.debug(LogMessage.format(\"extracted nc: '%s'; cnonce: '%s'\", this.nc, this.cnonce));\n\t\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\t\"DigestAuthenticationFilter.missingAuth\", new Object[] { this.section212response },\n\t\t\t\t\t\t\t\"Missing mandatory digest value; received header {0}\"));\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Check realm name equals what we expected\n\t\t\tif (!this.realm.equals(expectedRealm)) {\n\t\t\t\tthrow new BadCredentialsException(DigestAuthenticationFilter.this.messages.getMessage(\n\t\t\t\t\t\t\"DigestAuthenticationFilter.incorrectRealm\", new Object[] { this.realm, expectedRealm },\n\t\t\t\t\t\t\"Response realm name '{0}' does not match system realm name of '{1}'\"));\n\t\t\t}\n\t\t\t// Check nonce was Base64 encoded (as sent by DigestAuthenticationEntryPoint)\n\t\t\tfinal byte[] nonceBytes;\n\t\t\ttry {\n\t\t\t\tnonceBytes = Base64.getDecoder().decode(this.nonce.getBytes());\n\t\t\t}\n\t\t\tcatch (IllegalArgumentException ex) {\n\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(","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java#L367-L403","documentation":"The nonce sent by the client must be the Base64 value issued by DigestAuthenticationEntryPoint. validateAndDecode tries Base64-decoding the nonce and throws this BadCredentialsException if the value is not valid Base64, meaning the client tampered with, truncated, or fabricated the nonce.","triggerScenarios":"The nonce field in the Digest header is not valid Base64 — e.g. manually invented nonce values, URL-encoding artifacts ('+' turned into spaces, '/' or '=' stripped), truncated nonce after a proxy rewrite, or a client using a URL-safe Base64 variant the strict decoder rejects.","commonSituations":"Proxies/gateways re-encoding the Authorization header; frameworks decoding query/header values before the filter sees them; clients storing the nonce in a cookie/DB where padding '=' gets trimmed; hand-crafted digest clients generating their own nonces.","solutions":["Pass the nonce back to the server byte-for-byte exactly as it appeared in the WWW-Authenticate challenge — no re-encoding, trimming, or URL encoding.","Check intermediary proxies/CDNs for Authorization-header rewriting and disable it.","If storing the nonce client-side, use a binary-safe encoding (e.g. hex of UTF-8 bytes) and decode symmetrically before sending.","Ensure the client re-requests a fresh challenge (401 -> WWW-Authenticate) rather than reusing a stale or guessed nonce."],"exampleFix":"// before (client re-encodes)\nString nonce = URLEncoder.encode(challengeNonce, \"UTF-8\");\n// after\nString nonce = challengeNonce; // echo verbatim from WWW-Authenticate","handlingStrategy":"validation","validationCode":"try {\n    java.util.Base64.getDecoder().decode(nonce.getBytes(StandardCharsets.UTF_8));\n} catch (IllegalArgumentException e) {\n    throw new IllegalStateException(\"nonce is not valid Base64; request a fresh WWW-Authenticate challenge\");\n}\n","typeGuard":"boolean isServerNonce(String nonce) {\n    try { java.util.Base64.getDecoder().decode(nonce.getBytes(StandardCharsets.UTF_8)); return true; }\n    catch (IllegalArgumentException e) { return false; }\n}","tryCatchPattern":"try {\n    chain.doFilter(request, response);\n} catch (BadCredentialsException e) {\n    if (e.getMessage().contains(\"Nonce is not encoded in Base64\")) {\n        response.sendError(401, \"Invalid nonce; request a new challenge\");\n    }\n}","preventionTips":["Echo the nonce byte-for-byte from the challenge — never URL-encode or trim it","Store the nonce in a binary-safe form (hex/quoted) if persisting client-side","Disable proxies' Authorization-header rewriting","Always re-fetch a challenge instead of reusing guessed nonces"],"tags":["spring-security","digest-auth","base64","nonce"],"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"}