apereo/cas · error · InvalidLoginTimeException

Unable to login at this time

Error message

Unable to login at this time

What it means

JsonResourceAuthenticationHandler supports per-account availability windows formatted as 'start~end' datetimes. If the current UTC time is before the start or after the end of that window, it throws InvalidLoginTimeException 'Unable to login at this time'.

Solutions

  1. Widen or remove the availability window on the account in the JSON resource
  2. Confirm window datetimes are expressed in a timezone consistent with UTC comparison (prefer offsets like 2026-09-08T09:00:00+00:00)
  3. Check the server clock/timezone if the window looks correct
  4. If the account should be permanently available, clear the availability field entirely

Example fix

// before
"availability": "2024-01-01T00:00:00Z~2024-02-01T00:00:00Z"
// after
"availability": "2024-01-01T00:00:00Z~2030-01-01T00:00:00Z"
Defensive patterns

Strategy: validation

Validate before calling

ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
// parse account.availability 'start~end' and confirm now is inside before authenticating

Try / catch

try {
    return handler.authenticate(credential);
} catch (InvalidLoginTimeException e) {
    // inform user of allowed window
}

Prevention

When it happens

Trigger: authenticateUsernamePasswordInternal for an account whose availability field is non-blank and ZonedDateTime.now(UTC) is outside the parsed start~end range.

Common situations: Account temporarily enabled only during certain hours/dates and login attempted outside them; timezone confusion — window stored in local time but compared against UTC; window left set from a past temporary grant; malformed date failing conversion is a related failure.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-generic/src/main/java/org/apereo/cas/adaptors/generic/JsonResourceAuthenticationHandler.java:93

            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 clientInfo = ClientInfoHolder.getClientInfo();
        if (clientInfo != null && StringUtils.isNotBlank(account.getLocation())
            && !RegexUtils.find(account.getLocation(), clientInfo.getClientIpAddress())) {
            throw new InvalidLoginLocationException("Unable to login from this location");
        }

        if (StringUtils.isNotBlank(account.getAvailability())) {
            val range = Splitter.on("~").splitToList(account.getAvailability());
            val startDate = DateTimeUtils.convertToZonedDateTime(range.getFirst());
            val endDate = DateTimeUtils.convertToZonedDateTime(range.get(1));
            val now = ZonedDateTime.now(Clock.systemUTC());
            if (now.isBefore(startDate) || now.isAfter(endDate)) {
                throw new InvalidLoginTimeException("Unable to login at this time");
            }
        }

        val warnings = new ArrayList<MessageDescriptor>();
        if (account.getExpirationDate() != null) {
            val now = LocalDate.now(ZoneOffset.UTC);
            if (now.isEqual(account.getExpirationDate()) || now.isAfter(account.getExpirationDate())) {
                throw new AccountExpiredException();
            }
            if (getPasswordPolicyConfiguration() != null) {
                val warningPeriod = account.getExpirationDate()
                    .minusDays(getPasswordPolicyConfiguration().getPasswordWarningNumberOfDays());
                if (now.isAfter(warningPeriod) || now.isEqual(warningPeriod)) {
                    val daysRemaining = ChronoUnit.DAYS.between(now, account.getExpirationDate());
                    warnings.add(new DefaultMessageDescriptor(
                        "password.expiration.loginsRemaining",
                        "You have {0} logins remaining before you MUST change your password.",
                        new Serializable[]{daysRemaining}));

View on GitHub (pinned to e7288fc434)