apereo/cas · warning

Token [ ] has expired

Error message

Token [{}] has expired

What it means

JpaPasswordlessTokenRepository.findToken locates the stored passwordless token row but its decoded token is past its expiration time, so the repository logs a warning and returns Optional.empty as if the token did not exist. The user must request a new token.

Solutions

  1. Have the user request a new token
  2. Increase cas.authn.passwordless.tokens.time-to-kill if tokens expire too quickly
  3. Check server clock synchronization (NTP) across CAS nodes and the database
  4. Clean expired rows from the passwordless token table

Example fix

// before
cas.authn.passwordless.tokens.time-to-kill=PT1M
// after
cas.authn.passwordless.tokens.time-to-kill=PT5M
Defensive patterns

Strategy: fallback

Validate before calling

if (Instant.now().isAfter(token.getExpiration())) requestNewToken();

Try / catch

repository.findToken(id).orElseThrow(() -> new PasswordlessTokenExpiredException()); catch -> prompt re-request

Prevention

When it happens

Trigger: A passwordless user submits a token after cas.authn.passwordless.tokens.time-to-kill (or the token's expiration) has elapsed; findToken decodes the JPA row and authnToken.isExpired() is true.

Common situations: User waits too long before entering the emailed/OTP token; clock skew between CAS nodes; token expiration configured too short; stale rows in the JPA table never purged.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-passwordless-jpa/src/main/java/org/apereo/cas/impl/token/JpaPasswordlessTokenRepository.java:51

    private EntityManager entityManager;

    public JpaPasswordlessTokenRepository(final long tokenExpirationInSeconds,
                                          final CipherExecutor cipherExecutor) {
        super(tokenExpirationInSeconds, cipherExecutor);
    }

    @Override
    public Optional<PasswordlessAuthenticationToken> findToken(final String username) {
        val query = SELECT_QUERY.concat(" WHERE t.username = :username");
        val results = entityManager.createQuery(query, JpaPasswordlessAuthenticationEntity.class)
            .setParameter(QUERY_PARAM_USERNAME, username)
            .setMaxResults(1)
            .getResultList();
        if (!results.isEmpty()) {
            val token = results.getFirst();
            val authnToken = decodePasswordlessAuthenticationToken(token.getToken());
            if (authnToken.isExpired()) {
                LOGGER.warn("Token [{}] has expired", token);
                return Optional.empty();
            }
            LOGGER.debug("Located token [{}]", authnToken);
            return Optional.of(authnToken);
        }
        return Optional.empty();
    }

    @Override
    public void deleteTokens(final String username) {
        entityManager.createQuery(DELETE_QUERY.concat("WHERE t.username = :username"))
            .setParameter(QUERY_PARAM_USERNAME, username)
            .executeUpdate();
    }

    @Override
    public void deleteToken(final PasswordlessAuthenticationToken token) {
        val query = DELETE_QUERY.concat(" WHERE t.username = :username AND t.id = :id");

View on GitHub (pinned to e7288fc434)