spring-projects/spring-security · error · InvalidOneTimeTokenException

Invalid token

Error message

Invalid token

What it means

OneTimeTokenAuthenticationProvider throws InvalidOneTimeTokenException('Invalid token') when OneTimeTokenService.consume(token) returns null, meaning no unconsumed one-time token exists matching the presented username+token value. Tokens are single-use: they are deleted/invalidated on first consumption and may also have expired. This is an AuthenticationException handled by the OTT login filter's failure handler.

Source

Thrown at core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java:68

	private final UserDetailsService userDetailsService;

	private UserDetailsChecker userDetailsChecker = (user) -> {
	};

	public OneTimeTokenAuthenticationProvider(OneTimeTokenService oneTimeTokenService,
			UserDetailsService userDetailsService) {
		Assert.notNull(oneTimeTokenService, "oneTimeTokenService cannot be null");
		Assert.notNull(userDetailsService, "userDetailsService cannot be null");
		this.userDetailsService = userDetailsService;
		this.oneTimeTokenService = oneTimeTokenService;
	}

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		OneTimeTokenAuthenticationToken otpAuthenticationToken = (OneTimeTokenAuthenticationToken) authentication;
		OneTimeToken consumed = this.oneTimeTokenService.consume(otpAuthenticationToken);
		if (consumed == null) {
			throw new InvalidOneTimeTokenException("Invalid token");
		}
		try {
			UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
			this.userDetailsChecker.check(user);
			Collection<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());
			authorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));
			OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, authorities);
			authenticated.setDetails(otpAuthenticationToken.getDetails());
			return authenticated;
		}
		catch (UsernameNotFoundException ex) {
			throw new BadCredentialsException("Failed to authenticate the one-time token");
		}
	}

	@Override
	public boolean supports(Class<?> authentication) {
		return OneTimeTokenAuthenticationToken.class.isAssignableFrom(authentication);

View on GitHub (pinned to 96852e8860)

Solutions

  1. Issue a fresh one-time token (generate a new OneTimeToken via OneTimeTokenService) and resend the login link when a stale token is submitted
  2. Replace InMemoryOneTimeTokenService with a shared, persistent implementation so tokens survive restarts and work across multiple instances
  3. Increase the token TTL if email delivery delays are causing expiry
  4. Catch InvalidOneTimeTokenException in the authentication failure handler and show a 'link expired, request a new one' page with a link back to the OTT request page

Example fix

// before
@Bean
OneTimeTokenService oneTimeTokenService() { return new InMemoryOneTimeTokenService(); }
// after
@Bean
OneTimeTokenService oneTimeTokenService(JdbcTemplate jdbc) {
    JdbcOneTimeTokenService s = new JdbcOneTimeTokenService(jdbc);
    s.setCleanupCron(...); // shared store survives restarts and multi-instance deploys
    return s;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (token == null || token.isBlank()) { show("request a new login link"); return; }

Try / catch

try { return authManager.authenticate(ottToken); } catch (InvalidOneTimeTokenException e) { return redirect("/ott/generate?expired=1"); }

Prevention

When it happens

Trigger: Submitting the OTT login form after the token was already consumed once (page refresh / double submit); token expired per OneTimeTokenService TTL (e.g. InMemoryOneTimeToken default expiry); server restart evicting InMemoryOneTimeToken tokens; user typing/altering the token value in the URL or form; requesting a login link for one username but completing login for another.

Common situations: Users clicking an emailed magic link twice or bookmarking it; applications using the in-memory token service with multiple instances behind a load balancer (token issued by node A, validated at node B); long email delivery delay exceeding token TTL.

Understand the failure class

Related errors


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