spring-projects/spring-security · warning · InvalidCookieException
Cookie contained signature
Error message
Cookie contained signature '<actualTokenSignature>' but expected '<expectedTokenSignature>'
What it means
The remember-me cookie carries an HMAC signature computed over expiry time, username, password, and algorithm using the configured signing key. If the signature in the cookie does not match the recomputed expected signature, InvalidCookieException is thrown. This indicates the cookie was forged, corrupted, or signed with a different key/password.
Solutions
- Ensure the remember-me key is identical on all application instances (externalize to config)
- Invalidate and re-issue cookies after password changes — expected behavior since the signature includes the password
- Check the cookie was not modified in transit (secure transport, no tampering proxies)
- If key rotation is planned, plan for mass re-login or version the key
Example fix
// before (hardcoded divergent keys per node)
http.rememberMe(r -> r.key("node1-secret"));
// after (shared externalized key)
http.rememberMe(r -> r.key(env.getRequiredProperty("SECURITY_REMEMBER_ME_KEY"))); Defensive patterns
Strategy: validation
Validate before calling
// ensure all nodes share the same key at startup
@PostConstruct void checkKey() {
Assert.notNull(env.getProperty("security.remember-me.key"),
"remember-me key must be identical across nodes");
} Try / catch
try {
UserDetails u = rememberMeServices.autoLogin(request, response);
} catch (InvalidCookieException e) {
logger.debug("Remember-me signature mismatch; clearing cookie", e);
cookieClearingLogoutHandler.logout(request, response, null);
chain.doFilter(request, response);
} Prevention
- Externalize the remember-me key and deploy it identically to every node
- Expect cookies to be invalid after password changes; don't treat as a bug
- Enable HTTPS to prevent in-transit cookie modification
- Plan cookie invalidation windows around key rotations
When it happens
Trigger: processAutoLoginCookie computes makeTokenSignature(expiry, username, password, algorithm) and compares it with cookieTokens[3] using a non-constant-time-vs-equals check; any mismatch throws. Occurs when the shared key differs between servers, the user's password changed (signature includes password hash), or the cookie was altered.
Common situations: Signing key not identical across cluster nodes (each node rejects the other's cookies); user's password changed after cookie issuance (signature embeds password); cookie tampering attempts; key rotated without invalidating old cookies.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Autologin failed due to data access problem
- Can not set rememberMeCookieName and custom…
- Cookie token[1] did not contain a valid number (contained…
- Cookie token[1] has expired
- Cookie token did not contain 2 tokens, but contained…
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/ee2722ef9160e5c4.
Report an issue: GitHub.
Appendix: source
Thrown at web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java:159
+ " returned null for username " + cookieTokens[0] + ". " + "This is an interface contract violation");
// Check signature of token matches remaining details. Must do this after user
// lookup, as we need the DAO-derived password. If efficiency was a major issue,
// just add in a UserCache implementation, but recall that this method is usually
// only called once per HttpSession - if the token is valid, it will cause
// SecurityContextHolder population, whilst if invalid, will cause the cookie to
// be cancelled.
String actualTokenSignature = cookieTokens[2];
RememberMeTokenAlgorithm actualAlgorithm = this.matchingAlgorithm;
// If the cookie value contains the algorithm, we use that algorithm to check the
// signature
if (cookieTokens.length == 4) {
actualTokenSignature = cookieTokens[3];
actualAlgorithm = RememberMeTokenAlgorithm.valueOf(cookieTokens[2]);
}
String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(),
userDetails.getPassword(), actualAlgorithm);
if (!equals(expectedTokenSignature, actualTokenSignature)) {
throw new InvalidCookieException("Cookie contained signature '" + actualTokenSignature + "' but expected '"
+ expectedTokenSignature + "'");
}
return userDetails;
}
private boolean isValidCookieTokensLength(String[] cookieTokens) {
return cookieTokens.length == 3 || cookieTokens.length == 4;
}
private long getTokenExpiryTime(String[] cookieTokens) {
try {
return Long.valueOf(cookieTokens[1]);
}
catch (NumberFormatException nfe) {
throw new InvalidCookieException(
"Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1] + "')");
}
}View on GitHub (pinned to 96852e8860)