{"record":{"id":"75cd0a061747faa9","repo":"apereo/cas","slug":"failedloginexception","errorCode":null,"errorMessage":"FailedLoginException","messagePattern":"FailedLoginException","errorType":"exception","errorClass":"FailedLoginException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-redis-authentication/src/main/java/org/apereo/cas/redis/RedisAuthenticationHandler.java","lineNumber":41,"sourceCode":"\n    public RedisAuthenticationHandler(final String name,\n                                      final PrincipalFactory principalFactory, final Integer order,\n                                      final CasRedisTemplate redisTemplate) {\n        super(name, principalFactory, order);\n        this.redisTemplate = redisTemplate;\n    }\n\n    @Override\n    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(\n        final UsernamePasswordCredential credential,\n        final String originalPassword) throws Throwable {\n        val account = (RedisUserAccount) redisTemplate.opsForValue().get(credential.getUsername());\n        if (account == null) {\n            throw new AccountNotFoundException();\n        }\n        if (!getPasswordEncoder().matches(originalPassword, account.getPassword())) {\n            LOGGER.warn(\"Account password on record for [{}] does not match the given/encoded password\", credential.getId());\n            throw new FailedLoginException();\n        }\n        switch (account.getStatus()) {\n            case DISABLED -> throw new AccountDisabledException();\n            case EXPIRED -> throw new AccountExpiredException();\n            case LOCKED -> throw new AccountLockedException();\n            case MUST_CHANGE_PASSWORD -> throw new AccountPasswordMustChangeException();\n            case OK -> LOGGER.debug(\"Account status is OK\");\n        }\n        val principal = principalFactory.createPrincipal(account.getUsername(), account.getAttributes());\n        return createHandlerResult(credential, principal, new ArrayList<>());\n    }\n}\n","sourceCodeStart":23,"sourceCodeEnd":54,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-redis-authentication/src/main/java/org/apereo/cas/redis/RedisAuthenticationHandler.java#L23-L54","documentation":"RedisAuthenticationHandler throws FailedLoginException (no-arg) when the account exists in Redis but the stored (hashed) password does not match the presented password per the configured PasswordEncoder. The account lookup succeeded; this is purely a credential mismatch.","triggerScenarios":"getPasswordEncoder().matches(originalPassword, account.getPassword()) returns false for an existing RedisUserAccount.","commonSituations":"PasswordEncoder misconfigured so hashes were written with a different algorithm than CAS verifies with (e.g. bcrypt stored but plain/pbkdf2 expected); password changed elsewhere; provisioning stored the cleartext or double-hashed password.","solutions":["Re-provision the account password encoded with the same algorithm configured in cas.authn.password.encoding.*","Align the PasswordEncoder configuration between the provisioning writer and CAS","Test the encoder manually: encoder.matches(raw, storedHash) to isolate algorithm vs data problems","Reset the user's password through the normal flow"],"exampleFix":"// before: stored with different algorithm than CAS encoder\nredisTemplate.opsForValue().set(u, new RedisUserAccount(u, md5Hex(pw), Status.OK, attrs));\n// after: encode with the same encoder CAS is configured with\nredisTemplate.opsForValue().set(u, new RedisUserAccount(u, new BCryptPasswordEncoder().encode(pw), Status.OK, attrs));","handlingStrategy":"validation","validationCode":"RedisUserAccount acct = (RedisUserAccount) redisTemplate.opsForValue().get(username);\nif (acct != null && !passwordEncoder.matches(rawPassword, acct.getPassword())) {\n    // fail fast client-side / prompt reset\n}","typeGuard":null,"tryCatchPattern":"try {\n    authHandler.authenticate(credential);\n} catch (FailedLoginException e) {\n    // generic bad-credentials message; do not leak which check failed\n}","preventionTips":["Use one PasswordEncoder/algorithm for provisioning and CAS","Store only single-pass encoded hashes","Keep a smoke-test user per algorithm configuration"],"tags":["redis","bad-credentials","authentication","cas"],"backgroundTag":"authentication-required","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"}