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

  1. 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).
  2. Verify the relying party identifier matches the assertion's audience/realm; fix the configured realm if it drifted.
  3. Ensure the user is not resubmitting an old wresult token; request a fresh sign-in from the IdP.
  4. 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

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


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)