{"record":{"id":"91608f9fd23613ec","repo":"apereo/cas","slug":"token-cannot-be-used-before-s-and-now-is-s","errorCode":null,"errorMessage":"Token cannot be used before %s and now is %s","messagePattern":"Token cannot be used before (.+?) and now is (.+?)","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-heimdall/src/main/java/org/apereo/cas/heimdall/engine/DefaultAuthorizationPrincipalParser.java","lineNumber":141,"sourceCode":"    protected JWTClaimsSet buildClaimSetFromAuthentication(final String token) throws Throwable {\n        val usernamePass = Splitter.on(':').splitToList(EncodingUtils.decodeBase64ToString(token));\n        val credential = new UsernamePasswordCredential(usernamePass.getFirst(), usernamePass.getLast());\n        val authResultBuilder = authenticationSystemSupport.handleInitialAuthenticationTransaction(null, credential);\n        val authentication = authenticationSystemSupport.finalizeAllAuthenticationTransactions(authResultBuilder, null);\n        val claimsMap = buildClaimsFromAuthentication(authentication.getAuthentication());\n        return JWTClaimsSet.parse(claimsMap);\n    }\n\n    protected JWTClaimsSet validateClaims(final JWTClaimsSet claimsSet) {\n        val maxClockSkew = Beans.newDuration(casProperties.getAuthn().getOidc().getCore().getSkew()).toSeconds();\n        val now = new Date();\n        val exp = claimsSet.getExpirationTime();\n        if (exp != null && !DateUtils.isAfter(exp, now, maxClockSkew)) {\n            throw new AuthenticationException(\"Token has expired: %s and is after %s\".formatted(exp, now));\n        }\n        val nbf = claimsSet.getNotBeforeTime();\n        if (nbf != null && !DateUtils.isBefore(nbf, now, maxClockSkew)) {\n            throw new AuthenticationException(\"Token cannot be used before %s and now is %s\".formatted(nbf, now));\n        }\n        return claimsSet;\n    }\n\n    private Optional<JWTClaimsSet> getJwtClaimsSetFromAccessToken(final String token) {\n        try {\n            val ticket = ticketRegistry.getTicket(token, OAuth20AccessToken.class);\n            FunctionUtils.throwIf(ticket == null || ticket.isExpired(),\n                () -> new AuthenticationException(\"Token %s is not found or has expired\".formatted(token)));\n            val claimsMap = buildClaimsFromAuthentication(ticket.getAuthentication());\n            claimsMap.putAll(ticket.getClaims());\n            claimsMap.put(OAuth20Constants.SCOPE, ticket.getScopes());\n            claimsMap.put(OAuth20Constants.TOKEN, token);\n            return Optional.of(JWTClaimsSet.parse(claimsMap));\n        } catch (final Throwable e) {\n            LOGGER.debug(e.getMessage(), LOGGER.isTraceEnabled() ? e : null);\n            return Optional.empty();\n        }","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-heimdall/src/main/java/org/apereo/cas/heimdall/engine/DefaultAuthorizationPrincipalParser.java#L123-L159","documentation":"Also in validateClaims: if the token carries an nbf (not-before) claim that is still in the future beyond the allowed clock skew, the parser rejects it with AuthenticationException 'Token cannot be used before <nbf> and now is <now>'. The token is structurally valid but used too early.","triggerScenarios":"validateClaims receives a JWTClaimsSet whose notBeforeTime is non-null and not before now within maxClockSkew — i.e. the request arrives before the token's validity start.","commonSituations":"Clock skew where the client's clock is behind the CAS server; tokens pre-issued for scheduled/future use presented immediately; misconfigured issuer setting nbf to a future timestamp; distributed systems with unsynchronized clocks.","solutions":["Retry the request after the nbf timestamp passes","Fix clock synchronization (NTP) between token issuer and CAS server","Increase cas.authn.oidc.core.skew to tolerate modest drift","Have the issuer correct nbf generation if tokens are minted with a future not-before unintentionally"],"exampleFix":"// before: issuer sets nbf far in future\nlong nbf = now + 3600;\n// after\nlong nbf = now - 60;","handlingStrategy":"retry","validationCode":"Date nbf = claimsSet.getNotBeforeTime();\nif (nbf != null && nbf.after(new Date())) {\n    throw new IllegalStateException(\"Token not yet valid, wait until \" + nbf);\n}","typeGuard":null,"tryCatchPattern":"try {\n    return parser.claims(token);\n} catch (AuthenticationException e) {\n    // schedule retry after nbf passes\n}","preventionTips":["Check nbf before using freshly minted tokens","Keep issuer and CAS clocks synchronized","Ask issuer to set nbf slightly in the past when minting"],"tags":["jwt","not-before-claim","clock-skew","claims-validation"],"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"}