halo-dev/halo · warning · InvalidCookieException
Cookie contained signature '{}' but expected '{}'
Error message
Cookie contained signature '{}' but expected '{}' What it means
Halo's TokenBasedRememberMeServices re-derives the remember-me cookie signature from (tokenExpiryTime, username, password, algorithm) using the server key and compares it to the signature embedded in the cookie (token slot [2] or [3]). When the recomputed 'expected' signature does not equal the cookie's 'actual' signature, the cookie is treated as invalid/tampered and an InvalidCookieException is thrown. This mirrors Spring Security's classic TokenBasedRememberMeServices contract: the signature proves the cookie was minted by this server for this user's current password.
Source
Thrown at application/src/main/java/run/halo/app/security/authentication/rememberme/TokenBasedRememberMeServices.java:178
// be cancelled.
String actualTokenSignature;
String actualAlgorithm = DEFAULT_ALGORITHM;
// If the cookie value contains the algorithm, we use that algorithm to check the
// signature
if (cookieTokens.length == 4) {
actualTokenSignature = cookieTokens[3];
actualAlgorithm = cookieTokens[2];
} else {
actualTokenSignature = cookieTokens[2];
}
return makeTokenSignature(
tokenExpiryTime,
userDetails.getUsername(),
userDetails.getPassword(),
actualAlgorithm)
.doOnNext(expectedTokenSignature -> {
if (!equals(expectedTokenSignature, actualTokenSignature)) {
throw new InvalidCookieException(
"Cookie contained signature '" + actualTokenSignature
+ "' but expected '"
+ expectedTokenSignature + "'");
}
})
.thenReturn(userDetails);
});
}
protected boolean isTokenExpired(long tokenExpiryTime) {
return tokenExpiryTime < System.currentTimeMillis();
}
private long getTokenExpiryTime(String[] cookieTokens) {
try {
return Long.parseLong(cookieTokens[1]);
} catch (NumberFormatException nfe) {
throw new InvalidCookieException(View on GitHub (pinned to d2f5165f9c)
Solutions
- Have the user log in again interactively: cancelCookie() already expires the bad cookie and a fresh valid one is issued on next successful login (loginSuccess -> onLoginSuccess).
- Make the remember-me key a stable, explicitly configured value (e.g. halo.security.remember-me.key) instead of a generated/rotated one, so signatures survive restarts.
- If it occurs right after a password change, treat it as expected behavior; no code change is needed.
- Confirm the same key and the same password source is used across all replicas of a clustered deployment.
Example fix
// before: key is generated/rotated per restart -> all prior cookies fail signature check
// application.yaml
halo:
security:
remember-me:
key: ${REMEMBER_ME_KEY} # a stable, secret, shared value
// After a password change, simply let the stale cookie be rejected and re-issue on next login. Defensive patterns
Strategy: fallback
Validate before calling
// Cannot validate a cookie signature client-side; the server recomputes it.
// Guard at the API boundary by expecting auto-login to fail gracefully:
client.get("/api/console/user").onErrorResume(e -> {
if (e instanceof InvalidCookieException) {
return redirectToLogin(); // cookie is already cancelled server-side
}
return Mono.error(e);
}); Try / catch
// In a ServerWebExchange filter / onErrorResume:
.onErrorResume(InvalidCookieException.class, ex -> {
log.debug("Stale remember-me cookie rejected: {}", ex.getMessage());
return Mono.empty(); // treat as not-authenticated, prompt login
}) Prevention
- Configure a stable halo.security.remember-me.key shared across all replicas and restarts.
- Invalidate remember-me tokens when a user changes their password (expected, not a bug).
- Never share cookies between staging and production deployments.
- Monitor DEBUG logs for InvalidCookieException spikes to detect key drift or tampering campaigns.
When it happens
Trigger: Auto-login from a remember-me cookie after: the user's password hash changed since the cookie was issued; the server-side remember-me key changed (so makeTokenSignature produces a different HMAC); the cookie was manually edited; or a cookie minted by a different/older Halo deployment is presented. The check runs in processAutoLoginCookie -> makeTokenSignature(...).doOnNext(...).
Common situations: Server redeployed/restarted with a non-persistent or rotated remember-me key; password reset or password-change flow; user record now resolved from a different identity source with a different stored credential; cookie shared between environments (staging vs prod). Note handleError() only logs this at DEBUG and cancels the cookie, so to the user it looks like a silent re-login prompt.
Related errors
- Cannot resolve shared dependency ${root} from ${resolutionBa
- Shared dependency ${root} resolved to ${packageJson.name ||
- Cookie token[1] did not contain a valid number (contained '{
- Cookie token was not Base64 encoded; value was '{}'
- ESM UI provider output must expose a default PluginModule ex
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/9bb82a2eac1d2982.
Report an issue: GitHub.