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
- Issue a fresh one-time token (generate a new OneTimeToken via OneTimeTokenService) and resend the login link when a stale token is submitted
- Replace InMemoryOneTimeTokenService with a shared, persistent implementation so tokens survive restarts and work across multiple instances
- Increase the token TTL if email delivery delays are causing expiry
- 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
- Use a persistent OneTimeTokenService (JDBC) instead of in-memory for restarts and multi-instance deployments
- Set token TTLs comfortably above expected email delivery time
- In the authentication failure handler, direct users to request a fresh one-time token
- Treat token links as single-use: warn users on page refresh
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to authenticate the one-time token
- RunAsImplAuthenticationProvider.incorrectKey
- Authenticated principal required to operate with ACLs
- CasAuthenticationProvider.incorrectKey
- oidc_provider_not_configured
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/9bbd707c715edc65.
Report an issue: GitHub.