{"record":{"id":"78e54eb6b6e2b8c3","repo":"apereo/cas","slug":"passwordless-authentication-has-failed","errorCode":null,"errorMessage":"Passwordless authentication has failed","messagePattern":"Passwordless authentication has failed","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-passwordless-api/src/main/java/org/apereo/cas/authentication/PasswordlessTokenAuthenticationHandler.java","lineNumber":46,"sourceCode":"        this.passwordlessTokenRepository = passwordlessTokenRepository;\n    }\n\n    @Override\n    protected AuthenticationHandlerExecutionResult doAuthentication(final Credential credential, final Service service) throws Throwable {\n        val otc = (OneTimePasswordCredential) credential;\n        val token = passwordlessTokenRepository.findToken(otc.getId());\n        if (token.isPresent()) {\n            val passed = token\n                .map(PasswordlessAuthenticationToken::getToken)\n                .filter(StringUtils::isNotBlank)\n                .stream()\n                .allMatch(tk -> tk.equalsIgnoreCase(otc.getPassword()));\n            if (passed) {\n                val principal = principalFactory.createPrincipal(otc.getId());\n                return createHandlerResult(credential, principal, new ArrayList<>());\n            }\n        }\n        throw new FailedLoginException(\"Passwordless authentication has failed\");\n    }\n\n    @Override\n    public boolean supports(final Class<? extends Credential> clazz) {\n        return OneTimePasswordCredential.class.isAssignableFrom(clazz);\n    }\n\n    @Override\n    public boolean supports(final Credential credential) {\n        if (!(credential instanceof OneTimePasswordCredential)) {\n            LOGGER.debug(\"Credential is not one of one-time password and is not accepted by handler [{}]\", getName());\n            return false;\n        }\n        return true;\n    }\n}\n","sourceCodeStart":28,"sourceCodeEnd":63,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-passwordless-api/src/main/java/org/apereo/cas/authentication/PasswordlessTokenAuthenticationHandler.java#L28-L63","documentation":"FailedLoginException thrown by PasswordlessTokenAuthenticationHandler.doAuthentication when the submitted OneTimePasswordCredential does not equal (case-insensitively) any of the stored tokens for the user in the passwordless token repository. No principal/handler result is produced, so passwordless login fails.","triggerScenarios":"A user submits a one-time password via the passwordless flow and no token record in PasswordlessTokenRepository matches the user id with a token equal to the submitted credential password.","commonSituations":"User mistypes the code; token already consumed/deleted or expired in the token repository (in-memory repository lost entries on restart); user copies a token issued for another account; clock/token rotation replaced the token between request pages.","solutions":["Have the user re-request a fresh passwordless token and re-enter it before expiry.","Verify the PasswordlessTokenRepository implementation retains tokens across nodes/restarts (use a shared store like Redis/JDBC in clustered deployments, not the default in-memory map).","Check that clock skew or token TTL settings are not expiring tokens before the user can submit them.","Enable debug logging on the handler to confirm whether the user id matched but the token did not."],"exampleFix":"// before (clustered app with default repo)\n@Bean public PasswordlessTokenRepository repo() { return new DefaultPasswordlessTokenRepository(new HashMap>()); }\n// after\n@Bean public PasswordlessTokenRepository repo(RedisTemplate tpl) { return new RedisPasswordlessTokenRepository(tpl, expiration); }","handlingStrategy":"validation","validationCode":"// Caller-side pre-check in a custom flow\nboolean tokenExists = passwordlessTokenRepository.findToken(username)\n    .map(t -> t.equalsIgnoreCase(submittedOtp)).orElse(false);\nif (!tokenExists) { return error(\"invalid or expired code\"); }","typeGuard":null,"tryCatchPattern":"try { handler.authenticate(credential); } catch (FailedLoginException e) { model.addAttribute(\"error\", \"Invalid or expired code; request a new one\"); }","preventionTips":["Use a clustered/shared PasswordlessTokenRepository in multi-node deployments.","Set token TTL long enough for email/SMS delivery plus user entry.","Invalidate and reissue tokens cleanly when a new one is requested."],"tags":["authentication","one-time-password","passwordless"],"backgroundTag":"missing-credentials","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}