apereo/cas · error · AccountDisabledException

AccountDisabledException

Error message

AccountDisabledException

What it means

When the Redis account's Status is DISABLED, the handler throws AccountDisabledException immediately after successful password verification. The credentials were correct but the account is administratively disabled.

Solutions

  1. Set the account status to OK in Redis (or fix upstream and re-sync)
  2. Check the provisioning/sync job that populates status and re-run it after re-enabling the user
  3. Confirm your user-approval workflow flips the status before login is expected to succeed

Example fix

// before
account.setStatus(Status.DISABLED);
redisTemplate.opsForValue().set(username, account);
// after
account.setStatus(Status.OK);
redisTemplate.opsForValue().set(username, account);
Defensive patterns

Strategy: validation

Validate before calling

RedisUserAccount acct = (RedisUserAccount) redisTemplate.opsForValue().get(username);
if (acct != null && acct.getStatus() == Status.DISABLED) {
    // short-circuit: show 'account disabled' instead of attempting auth
}

Type guard

boolean isUsableAccount(RedisUserAccount a) { return a != null && a.getStatus() == Status.OK; }

Try / catch

try {
    authHandler.authenticate(credential);
} catch (AccountDisabledException e) {
    // account-disabled messaging / support link
}

Prevention

When it happens

Trigger: RedisUserAccount.getStatus() == Status.DISABLED on the matched account after a successful password check.

Common situations: Admin disabled the user in the source-of-truth and the status was synced to Redis; stale status left in Redis after re-enabling the user in the upstream system; provisioning writes DISABLED by default for new users pending approval.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/ffcd73fc931c844e. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-redis-authentication/src/main/java/org/apereo/cas/redis/RedisAuthenticationHandler.java:44

                                      final CasRedisTemplate redisTemplate) {
        super(name, principalFactory, order);
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
        final UsernamePasswordCredential credential,
        final String originalPassword) throws Throwable {
        val account = (RedisUserAccount) redisTemplate.opsForValue().get(credential.getUsername());
        if (account == null) {
            throw new AccountNotFoundException();
        }
        if (!getPasswordEncoder().matches(originalPassword, account.getPassword())) {
            LOGGER.warn("Account password on record for [{}] does not match the given/encoded password", credential.getId());
            throw new FailedLoginException();
        }
        switch (account.getStatus()) {
            case DISABLED -> throw new AccountDisabledException();
            case EXPIRED -> throw new AccountExpiredException();
            case LOCKED -> throw new AccountLockedException();
            case MUST_CHANGE_PASSWORD -> throw new AccountPasswordMustChangeException();
            case OK -> LOGGER.debug("Account status is OK");
        }
        val principal = principalFactory.createPrincipal(account.getUsername(), account.getAttributes());
        return createHandlerResult(credential, principal, new ArrayList<>());
    }
}

View on GitHub (pinned to e7288fc434)