apereo/cas · error · AccountExpiredException

AccountExpiredException

Error message

AccountExpiredException

What it means

When the Redis account's Status is EXPIRED, the handler throws AccountExpiredException after successful password verification. Authentication is refused because the account's validity window has lapsed per the stored status.

Solutions

  1. Extend the account's validity/renewal upstream and re-sync status to OK in Redis
  2. Manually update the RedisUserAccount status to OK if the upstream system is already correct
  3. Review the expiry/sync job schedule so renewals propagate promptly

Example fix

// before
account.setStatus(Status.EXPIRED);
redisTemplate.opsForValue().set(username, account);
// after (post-renewal sync)
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.EXPIRED) {
    // route user to account renewal flow before login
}

Type guard

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

Try / catch

try {
    authHandler.authenticate(credential);
} catch (AccountExpiredException e) {
    // account-expired messaging / renewal link
}

Prevention

When it happens

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

Common situations: Password or account expiry date passed and a sync job marks status EXPIRED in Redis; expiry not extended after renewal; Redis holds a stale snapshot from before the account was renewed upstream.

Related errors


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

Appendix: source

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

        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)