{"record":{"id":"ba1ed18f7a5391af","repo":"spring-projects/spring-security","slug":"cookie-token-did-not-contain-3-or-4-tokens-but-co","errorCode":null,"errorMessage":"Cookie token did not contain 3 or 4 tokens, but contained '[cookieTokens]'","messagePattern":"Cookie token did not contain 3 or 4 tokens, but contained '\\[cookieTokens\\]'","errorType":"exception","errorClass":"InvalidCookieException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java","lineNumber":129,"sourceCode":"\t * Construct the instance with the parameters provided.\n\t * @param key the signature key\n\t * @param userDetailsService the {@link UserDetailsService}\n\t * @param encodingAlgorithm the {@link RememberMeTokenAlgorithm} used to encode the\n\t * signature\n\t * @since 5.8\n\t */\n\tpublic TokenBasedRememberMeServices(String key, UserDetailsService userDetailsService,\n\t\t\tRememberMeTokenAlgorithm encodingAlgorithm) {\n\t\tsuper(key, userDetailsService);\n\t\tAssert.notNull(encodingAlgorithm, \"encodingAlgorithm cannot be null\");\n\t\tthis.encodingAlgorithm = encodingAlgorithm;\n\t}\n\n\t@Override\n\tprotected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,\n\t\t\tHttpServletResponse response) {\n\t\tif (!isValidCookieTokensLength(cookieTokens)) {\n\t\t\tthrow new InvalidCookieException(\n\t\t\t\t\t\"Cookie token did not contain 3 or 4 tokens, but contained '\" + Arrays.asList(cookieTokens) + \"'\");\n\t\t}\n\t\tlong tokenExpiryTime = getTokenExpiryTime(cookieTokens);\n\t\tif (isTokenExpired(tokenExpiryTime)) {\n\t\t\tthrow new InvalidCookieException(\"Cookie token[1] has expired (expired on '\" + new Date(tokenExpiryTime)\n\t\t\t\t\t+ \"'; current time is '\" + new Date() + \"')\");\n\t\t}\n\t\t// Check the user exists. Defer lookup until after expiry time checked, to\n\t\t// possibly avoid expensive database call.\n\t\tUserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);\n\t\tAssert.notNull(userDetails, () -> \"UserDetailsService \" + getUserDetailsService()\n\t\t\t\t+ \" returned null for username \" + cookieTokens[0] + \". \" + \"This is an interface contract violation\");\n\t\t// Check signature of token matches remaining details. Must do this after user\n\t\t// lookup, as we need the DAO-derived password. If efficiency was a major issue,\n\t\t// just add in a UserCache implementation, but recall that this method is usually\n\t\t// only called once per HttpSession - if the token is valid, it will cause\n\t\t// SecurityContextHolder population, whilst if invalid, will cause the cookie to\n\t\t// be cancelled.","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java#L111-L147","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log the user in again to issue a fresh, correctly formatted cookie","Ensure cookie names (and the key) don't collide between apps on the same domain","Clear old cookies after upgrading Spring Security versions that changed the cookie format","Check intermediaries (proxies, WAFs) are not truncating the Set-Cookie/Cookie headers"],"exampleFix":"// before (apps sharing cookie)\nhttp.rememberMe(r -> r.key(\"shared-key\")); // both apps, cookie 'remember-me' collides\n// after\nhttp.rememberMe(r -> r.rememberMeParameter(\"remember-me\").key(\"app1-key\")\n        .cookieName(\"app1-remember-me\"));","handlingStrategy":"validation","validationCode":"String[] parts = cookieValue.split(\":\");\nif (parts.length != 3 && parts.length != 4) {\n    // drop the cookie and redirect to login before invoking the filter\n    deleteRememberMeCookie(response);\n    return;\n}","typeGuard":"boolean isValidRememberMeCookie(String v) {\n    if (v == null) return false;\n    int n = v.split(\":\", -1).length;\n    return n == 3 || n == 4;\n}","tryCatchPattern":"try {\n    UserDetails u = rememberMeServices.autoLogin(request, response);\n} catch (InvalidCookieException e) {\n    cookieClearingLogoutHandler.logout(request, response, null);\n    chain.doFilter(request, response);\n}","preventionTips":["Use unique remember-me cookie names per app on shared domains","Clear legacy cookies when upgrading Spring Security versions","Test cookie handling behind proxies/CDNs","Never manually construct remember-me cookies"],"tags":["remember-me","spring-security","cookie","authentication"],"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-14T16:17:12.679Z"}