spring-projects/spring-security · error · InvalidCookieException

Cookie token did not contain 3 or 4 tokens, but contained '[

Error message

Cookie token did not contain 3 or 4 tokens, but contained '[cookieTokens]'

What it means

TokenBasedRememberMeServices expects the remember-me cookie to decode into exactly 3 tokens (username:expiry:signature) or 4 when an algorithm is included (username:expiry:algorithm:signature). If the decoded cookie splits into any other number of parts, InvalidCookieException is thrown with the actual token list. This guards against tampered, truncated, or foreign cookies.

Source

Thrown at web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java:129

	 * Construct the instance with the parameters provided.
	 * @param key the signature key
	 * @param userDetailsService the {@link UserDetailsService}
	 * @param encodingAlgorithm the {@link RememberMeTokenAlgorithm} used to encode the
	 * signature
	 * @since 5.8
	 */
	public TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService,
			RememberMeTokenAlgorithm encodingAlgorithm) {
		super(key, userDetailsService);
		Assert.notNull(encodingAlgorithm, "encodingAlgorithm cannot be null");
		this.encodingAlgorithm = encodingAlgorithm;
	}

	@Override
	protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
			HttpServletResponse response) {
		if (!isValidCookieTokensLength(cookieTokens)) {
			throw new InvalidCookieException(
					"Cookie token did not contain 3 or 4 tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
		}
		long tokenExpiryTime = getTokenExpiryTime(cookieTokens);
		if (isTokenExpired(tokenExpiryTime)) {
			throw new InvalidCookieException("Cookie token[1] has expired (expired on '" + new Date(tokenExpiryTime)
					+ "'; current time is '" + new Date() + "')");
		}
		// Check the user exists. Defer lookup until after expiry time checked, to
		// possibly avoid expensive database call.
		UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
		Assert.notNull(userDetails, () -> "UserDetailsService " + getUserDetailsService()
				+ " 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.

View on GitHub (pinned to 96852e8860)

Solutions

  1. Log the user in again to issue a fresh, correctly formatted cookie
  2. Ensure cookie names (and the key) don't collide between apps on the same domain
  3. Clear old cookies after upgrading Spring Security versions that changed the cookie format
  4. Check intermediaries (proxies, WAFs) are not truncating the Set-Cookie/Cookie headers

Example fix

// before (apps sharing cookie)
http.rememberMe(r -> r.key("shared-key")); // both apps, cookie 'remember-me' collides
// after
http.rememberMe(r -> r.rememberMeParameter("remember-me").key("app1-key")
        .cookieName("app1-remember-me"));
Defensive patterns

Strategy: validation

Validate before calling

String[] parts = cookieValue.split(":");
if (parts.length != 3 && parts.length != 4) {
    // drop the cookie and redirect to login before invoking the filter
    deleteRememberMeCookie(response);
    return;
}

Type guard

boolean isValidRememberMeCookie(String v) {
    if (v == null) return false;
    int n = v.split(":", -1).length;
    return n == 3 || n == 4;
}

Try / catch

try {
    UserDetails u = rememberMeServices.autoLogin(request, response);
} catch (InvalidCookieException e) {
    cookieClearingLogoutHandler.logout(request, response, null);
    chain.doFilter(request, response);
}

Prevention

When it happens

Trigger: processAutoLoginCookie receives a cookieTokens array whose length is not 3 or 4 — typically because the cookie value was corrupted, truncated by a proxy, encoded with a different delimiter, or produced by another application sharing the cookie name/key.

Common situations: Multiple apps on the same domain sharing the remember-me cookie name; cookie mangled by reverse proxy or CDN; version change in cookie format (e.g. 4-token format introduced for algorithm support) mixing old and new cookies; manual cookie editing.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/ba1ed18f7a5391af. Report an issue: GitHub.