{"record":{"id":"9563796d0c068375","repo":"spring-projects/spring-security","slug":"remember-me-login-has-expired","errorCode":null,"errorMessage":"Remember-me login has expired","messagePattern":"Remember-me login has expired","errorType":"exception","errorClass":"RememberMeAuthenticationException","httpStatus":null,"severity":"info","filePath":"web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java","lineNumber":119,"sourceCode":"\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);\n\t\t\tthrow new RememberMeAuthenticationException(\"Autologin failed due to data access problem\");\n\t\t}\n\t\treturn getUserDetailsService().loadUserByUsername(token.getUsername());\n\t}\n","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java#L101-L137","documentation":"processAutoLoginCookie validates the stored token's date against the configured token validity period. If token.getDate() + tokenValiditySeconds*1000 is earlier than the current time the remember-me login has expired and a RememberMeAuthenticationException is thrown, rejecting the cookie even though the series/token values match.","triggerScenarios":"Presenting a structurally valid series/token pair whose last-used timestamp is older than getTokenValiditySeconds() (default 14 days) — e.g. the user returns after a long absence, or tokenValiditySeconds was shortened below the age of stored tokens.","commonSituations":"Users away longer than the validity window; deployments that reduced tokenValiditySeconds, instantly invalidating existing cookies; servers with clock skew (clock moved back/forward) prematurely expiring or extending tokens; expired series kept by a cleanup job that removed them server-side before the cookie aged out (leading to 617 instead).","solutions":["Have the user log in again; the exception correctly forces re-authentication and issues a fresh cookie.","Increase tokenValiditySeconds (http.rememberMe().tokenValiditySeconds(...)) if a longer lifetime is acceptable for your security posture.","Keep the validity setting stable across deploys, or expect existing cookies to expire when you shorten it.","Synchronize server clocks (NTP) to avoid clock-skew-induced early expiry."],"exampleFix":"// before\nhttp.rememberMe().tokenValiditySeconds(3600); // 1h: users expire constantly\n// after\nhttp.rememberMe().tokenValiditySeconds(1209600); // 14 days (framework default)","handlingStrategy":"try-catch","validationCode":"PersistentRememberMeToken t = repo.getTokenForSeries(series);\nif (t != null && t.getDate().getTime() + validitySeconds * 1000L < System.currentTimeMillis()) {\n    // expired: skip remember-me, go to login page\n}","typeGuard":null,"tryCatchPattern":"try {\n    Authentication a = rememberMeServices.autoLogin(request, response);\n} catch (RememberMeAuthenticationException e) {\n    // expired cookie: proceed unauthenticated to the login page\n}","preventionTips":["Set tokenValiditySeconds appropriate for your users' return cadence","Keep validity settings stable across deployments","Synchronize server clocks with NTP","Clear expired series periodically so expiry manifests consistently"],"tags":["remember-me","token-expiry","session","spring-security"],"backgroundTag":"jwt-token-expired","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"}