spring-projects/spring-security · info · RememberMeAuthenticationException

No persistent token found for series id: <presentedSeries>

Error message

No persistent token found for series id: <presentedSeries>

What it means

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.

Source

Thrown at web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java:107

	 * @throws RememberMeAuthenticationException if there is no stored token corresponding
	 * to the submitted cookie, or if the token in the persistent store has expired.
	 * @throws InvalidCookieException if the cookie doesn't have two tokens as expected.
	 * @throws CookieTheftException if a presented series value is found, but the stored
	 * token is different from the one presented.
	 */
	@Override
	protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
			HttpServletResponse response) {
		if (cookieTokens.length != 2) {
			throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '"
					+ Arrays.asList(cookieTokens) + "'");
		}
		String presentedSeries = cookieTokens[0];
		String presentedToken = cookieTokens[1];
		PersistentRememberMeToken token = this.tokenRepository.getTokenForSeries(presentedSeries);
		if (token == null) {
			// No series match, so we can't authenticate using this cookie
			throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
		}
		// We have a match for this user/series combination
		if (!presentedToken.equals(token.getTokenValue())) {
			// Token doesn't match series value. Delete all logins for this user and throw
			// an exception to warn them.
			this.tokenRepository.removeUserTokens(token.getUsername());
			throw new CookieTheftException(this.messages.getMessage(
					"PersistentTokenBasedRememberMeServices.cookieStolen",
					"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
		}
		if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
			throw new RememberMeAuthenticationException("Remember-me login has expired");
		}
		// Token also matches, so login is valid. Update the token value, keeping the
		// *same* series number.
		this.logger.debug(LogMessage.format("Refreshing persistent login token for user '%s', series '%s'",
				token.getUsername(), token.getSeries()));
		PersistentRememberMeToken newToken = new PersistentRememberMeToken(token.getUsername(), token.getSeries(),

View on GitHub (pinned to 96852e8860)

Solutions

  1. Use JdbcTokenRepositoryImpl (or another persistent store) so series survive restarts and are shared by all nodes.
  2. 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.
  3. Increase tokenValiditySeconds or configure a cleanup policy if legitimate tokens are being purged too early.
  4. Verify all app instances share the same token repository and spring-security key.

Example fix

// before
http.rememberMe().tokenRepository(new InMemoryTokenRepositoryImpl()); // lost on restart
// after
http.rememberMe().tokenRepository(new JdbcTokenRepositoryImpl(dataSource, true)); // persistent, shared
Defensive patterns

Strategy: try-catch

Validate before calling

PersistentRememberMeToken t = repo.getTokenForSeries(series);
if (t == null) {
    // series unknown: redirect to login instead of attempting auto-login
}

Try / catch

try {
    Authentication a = rememberMeServices.autoLogin(request, response);
} catch (RememberMeAuthenticationException e) {
    // cookie no longer valid: force normal login, do not treat as 500
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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