{"record":{"id":"8ec0ae2cbd1e215a","repo":"spring-projects/spring-security","slug":"failed-to-authenticate-the-one-time-token","errorCode":null,"errorMessage":"Failed to authenticate the one-time token","messagePattern":"Failed to authenticate the one-time token","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java","lineNumber":80,"sourceCode":"\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);\n\t}\n\n\t/**\n\t * Use this {@link UserDetailsChecker} to verify the status of the loaded\n\t * {@link UserDetails} after authentication.\n\t *\n\t * <p>\n\t * By default, no checks are performed, keeping this provider's behavior consistent\n\t * with earlier versions of Spring Security. To reject authentication for accounts\n\t * that are locked, disabled, or expired, provide a\n\t * {@link AccountStatusUserDetailsChecker}.\n\t * @param userDetailsChecker the {@link UserDetailsChecker} to use","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java#L62-L98","documentation":"OneTimeTokenAuthenticationProvider catches UsernameNotFoundException from userDetailsService.loadUserByUsername(consumed.getUsername()) and rethrows BadCredentialsException('Failed to authenticate the one-time token'). The opaque message prevents attackers from discovering which usernames exist. This happens when the token is valid but no user account matches the username stored with the token.","triggerScenarios":"A one-time token was generated for a username that does not exist (e.g. attacker-supplied or misspelled username on the OTT request form); the user account was deleted or renamed between token generation and consumption; the UserDetailsService bean in use does not include the user (e.g. wrong realm/tenant).","commonSituations":"OTT login pages that accept arbitrary email input without verifying account existence (by design, to avoid user enumeration); user deactivated between requesting and clicking the login link; multi-tenant apps where the token was issued in a different tenant context.","solutions":["Verify the username exists and the account is enabled before generating the one-time token (silently skip or handle unknown users to avoid token spam)","Catch BadCredentialsException in the OTT failure handler and show a generic error directing the user to request a new token or use another login method","Check that the configured UserDetailsService is the one holding the target users (correct tenant/realm/user registry)","Confirm case-sensitivity: normalize usernames (e.g. lowercase emails) both at token generation and lookup"],"exampleFix":"// before\noneTimeTokenService.generate(new GenerateOneTimeTokenRequest(username)); // username may not exist\n// after\nif (userDetailsService instanceof UserDetailsManager m && m.userExists(username)) {\n    oneTimeTokenService.generate(new GenerateOneTimeTokenRequest(username));\n}","handlingStrategy":"try-catch","validationCode":"if (username == null || username.isBlank()) { throw new IllegalArgumentException(\"username required to generate one-time token\"); }","typeGuard":null,"tryCatchPattern":"try { return authManager.authenticate(ottToken); } catch (BadCredentialsException e) { log.debug(\"OTT login failed: unknown or invalid user\"); return redirect(\"/login?ottFailed=1\"); }","preventionTips":["Normalize usernames (trim/lowercase emails) at both token generation and UserDetailsService lookup","Ensure the OTT request page cannot be abused to spam tokens for arbitrary usernames (rate-limit)","Use the same UserDetailsService for OTT token issuance and authentication","Keep account lifecycle (deletion/renaming) consistent with outstanding tokens; expire tokens on user deletion"],"tags":["authentication","spring-security","one-time-token","bad-credentials"],"backgroundTag":"bad-credentials","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"}