{"record":{"id":"bd58858b1080e159","repo":"spring-projects/spring-security","slug":"invalid-remember-me-token-series-token-mismatch","errorCode":null,"errorMessage":"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.","messagePattern":"Invalid remember-me token \\(Series/token\\) mismatch\\. Implies previous cookie theft attack\\.","errorType":"exception","errorClass":"CookieTheftException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java","lineNumber":114,"sourceCode":"\tprotected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,\n\t\t\tHttpServletResponse response) {\n\t\tif (cookieTokens.length != 2) {\n\t\t\tthrow new InvalidCookieException(\"Cookie token did not contain \" + 2 + \" tokens, but contained '\"\n\t\t\t\t\t+ Arrays.asList(cookieTokens) + \"'\");\n\t\t}\n\t\tString presentedSeries = cookieTokens[0];\n\t\tString presentedToken = cookieTokens[1];\n\t\tPersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);\n\t\tif (token == null) {\n\t\t\t// No series match, so we can't authenticate using this cookie\n\t\t\tthrow new RememberMeAuthenticationException(\"No persistent token found for series id: \" + presentedSeries);\n\t\t}\n\t\t// We have a match for this user/series combination\n\t\tif (!presentedToken.equals(token.getTokenValue())) {\n\t\t\t// Token doesn't match series value. Delete all logins for this user and throw\n\t\t\t// an exception to warn them.\n\t\t\tthis.tokenRepository.removeUserTokens(token.getUsername());\n\t\t\tthrow new CookieTheftException(this.messages.getMessage(\n\t\t\t\t\t\"PersistentTokenBasedRememberMeServices.cookieStolen\",\n\t\t\t\t\t\"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.\"));\n\t\t}\n\t\tif (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {\n\t\t\tthrow new RememberMeAuthenticationException(\"Remember-me login has expired\");\n\t\t}\n\t\t// Token also matches, so login is valid. Update the token value, keeping the\n\t\t// *same* series number.\n\t\tthis.logger.debug(LogMessage.format(\"Refreshing persistent login token for user '%s', series '%s'\",\n\t\t\t\ttoken.getUsername(), token.getSeries()));\n\t\tPersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(),\n\t\t\t\tgenerateTokenData(), new Date());\n\t\ttry {\n\t\t\tthis.tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(), newToken.getDate());\n\t\t\taddCookie(newToken, request, response);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthis.logger.error(\"Failed to update token: \", ex);","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java#L96-L132","documentation":"When the series is found but the presented token value differs from the stored one, the service assumes the stored token was already used by an attacker (the token is rotated after each login) and throws CookieTheftException after deleting all of the user's remember-me tokens. This is a deliberate security signal indicating a probable cookie replay/theft attack.","triggerScenarios":"processAutoLoginCookie receiving a valid series id whose presentedToken does not equal token.getTokenValue() — typically replaying an old cookie after the legitimate user's browser already consumed the previous token and triggered rotation; two devices/browsers sharing or copying the same remember-me cookie; restoring an old cookie from a backup.","commonSituations":"Users copying remember-me cookies between browsers or machines; cookie restore tools/sync software reverting to a previous cookie value; double-submit race where two requests with the same cookie arrive concurrently (one rotates, the other then mismatches); attackers replaying stolen cookies after the victim logged in again.","solutions":["Treat CookieTheftException as a security event: log it, notify the user, and force full re-authentication (the framework already removes all the user's remember-me tokens).","Educate users not to copy remember-me cookies between browsers/devices; each device should log in independently.","Enable async token rotation or account for concurrent-request races if legitimate users trigger this during parallel requests (consider upgrading to a version with safer rotation, or accept occasional false positives).","Ensure cookie backups/sync tools do not restore old remember-me cookies."],"exampleFix":"// before\n// old cookie replayed after rotation -> CookieTheftException\n// after\nhttp.rememberMe()\n    .tokenRepository(jdbcTokenRepository)\n    .useSecureCookie(true)\n    .rememberMeServices(new PersistentTokenBasedRememberMeServices(key, service, jdbcTokenRepository) {\n        @Override\n        protected void onLoginSuccess(HttpServletRequest req, HttpServletResponse res,\n                Authentication auth) { /* rotate per-device; keep one series per device */ }\n    });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    Authentication a = rememberMeServices.autoLogin(request, response);\n} catch (CookieTheftException e) {\n    securityAuditLog.warn(\"Possible remember-me cookie theft\", e);\n    // user's tokens already removed: redirect to login, consider notifying user\n}","preventionTips":["Never share or copy remember-me cookies between browsers/devices","Monitor CookieTheftException occurrences as security signals","Be aware of concurrent-request races with token rotation in old versions","Use secure/httpOnly cookies and HTTPS to reduce theft risk"],"tags":["remember-me","security","cookie-theft","spring-security"],"backgroundTag":"token-mismatch","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"}