apereo/cas · error · IllegalArgumentException
Could not validate the provided assertion
Error message
Could not validate the provided assertion
What it means
Thrown by WsFederationResponseValidator.buildCredentialsFromAssertion when the WsFederationCredential fails isValid(rpId, identityProviderIdentifier, tolerance). The SAML assertion is blank or no longer valid: its conditions (not-before/not-on-or-after) fail against the configured tolerance, or its audience/issuer does not match the RP and IdP identifiers.
Solutions
- Synchronize clocks (NTP) between the CAS server and the IdP, or increase the tolerance in WsFederationConfiguration (e.g. setTolerance(300000) for a few minutes of skew).
- Verify the relying party identifier matches the assertion's audience/realm; fix the configured realm if it drifted.
- Ensure the user is not resubmitting an old wresult token; request a fresh sign-in from the IdP.
- Enable debug logging to see which validity condition (time window vs RP/IdP match) rejected the credential.
Example fix
// before: zero tolerance fails on any clock skew configuration.setTolerance(0); // after: allow 5 minutes of clock drift configuration.setTolerance(300000);
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check time synchronization tolerance is non-trivial
if (configuration.getTolerance() == 0) {
LOGGER.warn("Tolerance is 0; any clock skew between CAS and IdP will invalidate assertions");
} Type guard
function hasValidTolerance(config) {
const t = config.getTolerance?.();
return typeof t === 'number' && t > 0 && t <= 15 * 60 * 1000;
} Try / catch
try {
validator.validateWsFederationAuthenticationRequest(context);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Could not validate the provided assertion")) {
LOGGER.error("Assertion expired or outside validity window; check clocks and tolerance");
return redirectToldPForFreshSignin();
}
throw e;
} Prevention
- Run NTP on CAS servers and set a tolerance of a few minutes to absorb normal clock drift.
- Reject stale tokens early: never cache or replay wresult values across requests.
- Monitor time offsets between CAS and IdP hosts in operations dashboards.
When it happens
Trigger: credential.isValid returns false because the assertion's NotBefore/NotOnOrAfter window does not cover 'now' (with configured tolerance), or the audience/RP identifier conditions in the assertion do not match the computed relying party identifier.
Common situations: Clock skew between the CAS server and the IdP (assertion appears not-yet-valid or expired), stale/replayed tokens, tolerance (setTolerance) too small for real clock drift, or an RP identifier mismatch making the audience condition fail.
Related errors
- Token has expired: and is after
- Token cannot be used before
- Proof iat is in the future
- Proof JWT is too old
- No groovy script cache manager is available to execute…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/71d6e907510fb5e7.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-wsfederation-webflow/src/main/java/org/apereo/cas/web/flow/WsFederationResponseValidator.java:103
if (credential == null) {
LOGGER.error("No credential could be extracted from [{}] based on relying party identifier [{}] and identity provider identifier [{}]",
assertion.getKey(), rpId, configuration.getIdentityProviderIdentifier());
throw new IllegalArgumentException("Could not extract and identify credentials");
}
if (credential.isValid(rpId, configuration.getIdentityProviderIdentifier(), configuration.getTolerance())) {
val currentAttributes = credential.getAttributes();
LOGGER.debug("Validated assertion for the created credential successfully and located attributes [{}]", currentAttributes);
if (configuration.getAttributeMutator() != null) {
LOGGER.debug("Modifying credential attributes based on [{}]", configuration.getAttributeMutator().getClass().getSimpleName());
val attributes = configuration.getAttributeMutator().modifyAttributes(currentAttributes);
LOGGER.debug("Finalized credential attributes are [{}]", attributes);
credential.setAttributes(attributes);
}
} else {
LOGGER.error("SAML assertions are blank or no longer valid based on RP identifier [{}] and identity provider identifier [{}]",
rpId, configuration.getIdentityProviderIdentifier());
throw new IllegalArgumentException("Could not validate the provided assertion");
}
WebUtils.putServiceIntoFlowScope(context, service);
LOGGER.debug("Creating final authentication result based on the given credential");
val authenticationResult = this.authenticationSystemSupport.finalizeAuthenticationTransaction(service, credential);
WebUtils.putAuthenticationResult(authenticationResult, context);
WebUtils.putAuthentication(authenticationResult.getAuthentication(), context);
WebUtils.putCredential(context, credential);
WebUtils.putServiceIntoFlowScope(context, service);
LOGGER.info("Token validated and new [{}] created: [{}]", credential.getClass().getName(), credential);
} catch (final Exception e) {
LoggingUtils.error(LOGGER, e);
throw e;
}
}
}
View on GitHub (pinned to e7288fc434)