{"record":{"id":"11b49453c11f9138","repo":"spring-projects/spring-security","slug":"no-persistent-token-found-for-series-id-presente","errorCode":null,"errorMessage":"No persistent token found for series id: <presentedSeries>","messagePattern":"No persistent token found for series id: <presentedSeries>","errorType":"exception","errorClass":"RememberMeAuthenticationException","httpStatus":null,"severity":"info","filePath":"web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java","lineNumber":107,"sourceCode":"\t * @throws RememberMeAuthenticationException if there is no stored token corresponding\n\t * to the submitted cookie, or if the token in the persistent store has expired.\n\t * @throws InvalidCookieException if the cookie doesn't have two tokens as expected.\n\t * @throws CookieTheftException if a presented series value is found, but the stored\n\t * token is different from the one presented.\n\t */\n\t@Override\n\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(),","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java#L89-L125","documentation":"After splitting the cookie into series and token, processAutoLoginCookie asks the token repository for the series. If the repository returns null the series is unknown (expired, purged, or never existed) and a RememberMeAuthenticationException is thrown, so the remember-me cookie is rejected and the user must log in normally.","triggerScenarios":"Presenting a remember-me cookie whose series id has no entry in the tokenRepository — after server restart with InMemoryTokenRepositoryImpl, after the token was deleted (password change, removeUserTokens, expiry cleanup), or against a different database/key than the one that issued the cookie.","commonSituations":"In-memory repository losing state on redeploy/restart while users keep old cookies; token cleanup jobs removing old series; switching between dev and prod databases; load-balanced nodes pointing at different token stores.","solutions":["Use JdbcTokenRepositoryImpl (or another persistent store) so series survive restarts and are shared by all nodes.","Accept that this exception means the user must re-authenticate; ensure RememberMeAuthenticationException is handled by returning the user to the login page, not a 500.","Increase tokenValiditySeconds or configure a cleanup policy if legitimate tokens are being purged too early.","Verify all app instances share the same token repository and spring-security key."],"exampleFix":"// before\nhttp.rememberMe().tokenRepository(new InMemoryTokenRepositoryImpl()); // lost on restart\n// after\nhttp.rememberMe().tokenRepository(new JdbcTokenRepositoryImpl(dataSource, true)); // persistent, shared","handlingStrategy":"try-catch","validationCode":"PersistentRememberMeToken t = repo.getTokenForSeries(series);\nif (t == null) {\n    // series unknown: redirect to login instead of attempting auto-login\n}","typeGuard":null,"tryCatchPattern":"try {\n    Authentication a = rememberMeServices.autoLogin(request, response);\n} catch (RememberMeAuthenticationException e) {\n    // cookie no longer valid: force normal login, do not treat as 500\n}","preventionTips":["Use JdbcTokenRepositoryImpl or another persistent, shared store across nodes","Point all app instances at the same token repository and key","Set cleanup/retention policies longer than tokenValiditySeconds","Warn users that server restarts with the in-memory repository log everyone out of remember-me"],"tags":["remember-me","token","session","spring-security"],"backgroundTag":"record-not-found","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"}