{"record":{"id":"cef46ca8e9a40574","repo":"apereo/cas","slug":"token-has-expired","errorCode":null,"errorMessage":"Token [{}] has expired","messagePattern":"Token \\[(.+?)\\] has expired","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-passwordless-jpa/src/main/java/org/apereo/cas/impl/token/JpaPasswordlessTokenRepository.java","lineNumber":51,"sourceCode":"    private EntityManager entityManager;\n\n    public JpaPasswordlessTokenRepository(final long tokenExpirationInSeconds,\n                                          final CipherExecutor cipherExecutor) {\n        super(tokenExpirationInSeconds, cipherExecutor);\n    }\n\n    @Override\n    public Optional<PasswordlessAuthenticationToken> findToken(final String username) {\n        val query = SELECT_QUERY.concat(\" WHERE t.username = :username\");\n        val results = entityManager.createQuery(query, JpaPasswordlessAuthenticationEntity.class)\n            .setParameter(QUERY_PARAM_USERNAME, username)\n            .setMaxResults(1)\n            .getResultList();\n        if (!results.isEmpty()) {\n            val token = results.getFirst();\n            val authnToken = decodePasswordlessAuthenticationToken(token.getToken());\n            if (authnToken.isExpired()) {\n                LOGGER.warn(\"Token [{}] has expired\", token);\n                return Optional.empty();\n            }\n            LOGGER.debug(\"Located token [{}]\", authnToken);\n            return Optional.of(authnToken);\n        }\n        return Optional.empty();\n    }\n\n    @Override\n    public void deleteTokens(final String username) {\n        entityManager.createQuery(DELETE_QUERY.concat(\"WHERE t.username = :username\"))\n            .setParameter(QUERY_PARAM_USERNAME, username)\n            .executeUpdate();\n    }\n\n    @Override\n    public void deleteToken(final PasswordlessAuthenticationToken token) {\n        val query = DELETE_QUERY.concat(\" WHERE t.username = :username AND t.id = :id\");","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-passwordless-jpa/src/main/java/org/apereo/cas/impl/token/JpaPasswordlessTokenRepository.java#L33-L69","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Have the user request a new token","Increase cas.authn.passwordless.tokens.time-to-kill if tokens expire too quickly","Check server clock synchronization (NTP) across CAS nodes and the database","Clean expired rows from the passwordless token table"],"exampleFix":"// before\ncas.authn.passwordless.tokens.time-to-kill=PT1M\n// after\ncas.authn.passwordless.tokens.time-to-kill=PT5M","handlingStrategy":"fallback","validationCode":"if (Instant.now().isAfter(token.getExpiration())) requestNewToken();","typeGuard":null,"tryCatchPattern":"repository.findToken(id).orElseThrow(() -> new PasswordlessTokenExpiredException()); catch -> prompt re-request","preventionTips":["Set a generous time-to-kill for tokens","Schedule purge jobs for expired rows","Synchronize clocks via NTP"],"tags":["passwordless","token","expiration","jpa"],"backgroundTag":"jwt-token-expired","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"}