{"record":{"id":"9bbd707c715edc65","repo":"spring-projects/spring-security","slug":"invalid-token","errorCode":null,"errorMessage":"Invalid token","messagePattern":"Invalid token","errorType":"exception","errorClass":"InvalidOneTimeTokenException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java","lineNumber":68,"sourceCode":"\tprivate final UserDetailsService userDetailsService;\n\n\tprivate UserDetailsChecker userDetailsChecker = (user) -> {\n\t};\n\n\tpublic OneTimeTokenAuthenticationProvider(OneTimeTokenService oneTimeTokenService,\n\t\t\tUserDetailsService userDetailsService) {\n\t\tAssert.notNull(oneTimeTokenService, \"oneTimeTokenService cannot be null\");\n\t\tAssert.notNull(userDetailsService, \"userDetailsService cannot be null\");\n\t\tthis.userDetailsService = userDetailsService;\n\t\tthis.oneTimeTokenService = oneTimeTokenService;\n\t}\n\n\t@Override\n\tpublic Authentication authenticate(Authentication authentication) throws AuthenticationException {\n\t\tOneTimeTokenAuthenticationToken otpAuthenticationToken = (OneTimeTokenAuthenticationToken) authentication;\n\t\tOneTimeToken consumed = this.oneTimeTokenService.consume(otpAuthenticationToken);\n\t\tif (consumed == null) {\n\t\t\tthrow new InvalidOneTimeTokenException(\"Invalid token\");\n\t\t}\n\t\ttry {\n\t\t\tUserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());\n\t\t\tthis.userDetailsChecker.check(user);\n\t\t\tCollection<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());\n\t\t\tauthorities.add(FactorGrantedAuthority.fromAuthority(AUTHORITY));\n\t\t\tOneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, authorities);\n\t\t\tauthenticated.setDetails(otpAuthenticationToken.getDetails());\n\t\t\treturn authenticated;\n\t\t}\n\t\tcatch (UsernameNotFoundException ex) {\n\t\t\tthrow new BadCredentialsException(\"Failed to authenticate the one-time token\");\n\t\t}\n\t}\n\n\t@Override\n\tpublic boolean supports(Class<?> authentication) {\n\t\treturn OneTimeTokenAuthenticationToken.class.isAssignableFrom(authentication);","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java#L50-L86","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n@Bean\nOneTimeTokenService oneTimeTokenService() { return new InMemoryOneTimeTokenService(); }\n// after\n@Bean\nOneTimeTokenService oneTimeTokenService(JdbcTemplate jdbc) {\n    JdbcOneTimeTokenService s = new JdbcOneTimeTokenService(jdbc);\n    s.setCleanupCron(...); // shared store survives restarts and multi-instance deploys\n    return s;\n}","handlingStrategy":"try-catch","validationCode":"if (token == null || token.isBlank()) { show(\"request a new login link\"); return; }","typeGuard":null,"tryCatchPattern":"try { return authManager.authenticate(ottToken); } catch (InvalidOneTimeTokenException e) { return redirect(\"/ott/generate?expired=1\"); }","preventionTips":["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"],"tags":["authentication","spring-security","one-time-token","expired-token"],"backgroundTag":"invalid-one-time-token","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"}