{"record":{"id":"326745e1300faa18","repo":"apereo/cas","slug":"token-s-has-expired","errorCode":null,"errorMessage":"Token %s has expired","messagePattern":"Token (.+?) has expired","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java","lineNumber":46,"sourceCode":"\n    private final TicketRegistry ticketRegistry;\n\n    private final CasConfigurationProperties casProperties;\n\n    private final QRAuthenticationDeviceRepository deviceRepository;\n\n    @Override\n    public QRAuthenticationTokenValidationResult validate(final QRAuthenticationTokenValidationRequest request) {\n        val claims = jwtBuilder.unpack(request.getRegisteredService(), request.getToken());\n        LOGGER.trace(\"Unpacked QR token as [{}]\", claims);\n\n        val tgt = ticketRegistry.getTicket(claims.getJWTID(), TicketGrantingTicket.class);\n        val dt = DateTimeUtils.localDateTimeOf(claims.getExpirationTime());\n\n        val now = LocalDateTime.now(Clock.systemUTC());\n        if (now.isAfter(dt)) {\n            LOGGER.trace(\"Comparing now at [{}] with token's expiration time [{}]\", now, dt);\n            throw new AuthenticationException(String.format(\"Token %s has expired\", tgt.getId()));\n        }\n\n        val authentication = tgt.getAuthentication();\n        LOGGER.trace(\"Authentication attempt linked to [{}] is [{}]\", tgt.getId(), authentication);\n\n        if (!authentication.getPrincipal().getId().equals(claims.getSubject())) {\n            val message = String.format(\"Token %s does not belong to the assigned principal\", claims.getSubject());\n            throw new AuthenticationException(message);\n        }\n\n        if (!claims.getIssuer().equals(casProperties.getServer().getPrefix())) {\n            val message = String.format(\"Token %s has an invalid issuer %s that does not match %s\", tgt.getId(),\n                claims.getIssuer(), casProperties.getServer().getPrefix());\n            throw new AuthenticationException(message);\n        }\n\n        val tokenDeviceId = FunctionUtils.doUnchecked(() -> claims.getStringClaim(QRAuthenticationConstants.QR_AUTHENTICATION_DEVICE_ID));\n        if (!Strings.CI.equals(tokenDeviceId, request.getDeviceId())) {","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-qr-authentication/src/main/java/org/apereo/cas/qr/validation/DefaultQRAuthenticationTokenValidatorService.java#L28-L64","documentation":"AuthenticationException thrown by DefaultQRAuthenticationTokenValidatorService.validate when the JWT claim's expiration time is in the past relative to the current UTC clock. The token id references a TicketGrantingTicket in the ticket registry, but the token itself has a fixed expiry that must not have elapsed.","triggerScenarios":"now.isAfter(localDateTimeOf(claims.getExpirationTime())) when validating a QR token: the token was minted with a short TTL and submitted after expiry.","commonSituations":"User scanned an old QR code; long delay between token generation and validation; server clock skewed forward; QR token TTL property set too short for the login UX.","solutions":["Refresh the QR code on the client and retry immediately.","Increase the QR token expiration (qr authentication token expiration policy) in cas.authn.qr properties.","Synchronize server clocks via NTP, especially across clustered nodes.","Check client-side auto-refresh of the QR image so expired tokens are not displayed."],"exampleFix":"// before\ncas.authn.qr.token.expiration-time-in-seconds=30\n// after\ncas.authn.qr.token.expiration-time-in-seconds=120","handlingStrategy":"validation","validationCode":"// Decode and check expiry before validating\nlong exp = claims.getExpirationTime().getTime();\nif (Instant.now().isAfter(Instant.ofEpochSecond(exp))) { refreshQrCode(); }","typeGuard":null,"tryCatchPattern":"try { validatorService.validate(request); } catch (AuthenticationException e) { if (e.getMessage().contains(\"has expired\")) { return newQrCodeEvent(); } throw e; }","preventionTips":["Set a QR token TTL that comfortably covers user scan time.","Auto-refresh the QR code on the client before expiry.","NTP-sync all servers performing validation."],"tags":["jwt","token-expired","qr-authentication"],"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"}