apereo/cas · warning
Audience [ ] is invalid where the expected audience should…
Error message
Audience [{}] is invalid where the expected audience should be [{}] What it means
WsFederationCredential.isValid checks the credential's audience, issuer, and timestamps against expected values. This warning is logged and the method returns false when the audience extracted from the token does not match (case-insensitively) the expected audience — the CAS relying-party identifier configured for the WS-Federation RP.
Solutions
- Align the audience identifier: make the IdP's relying party ID match expectedAudience, or fix the expected-audience configuration in CAS to match wtrealm exactly (case-insensitive, but string-exact otherwise).
- Check for trailing-slash or host-name differences between wtrealm and the configured value.
- Confirm the login request used the correct realm/wtrealm parameter for this CAS instance.
- Enable debug logging to compare the token's audience against the configured one.
Example fix
// before // IdP RP identifier: https://app.example.edu/cas/ (trailing slash) cas.authn.wsfed[0].identity-attribute=https://app.example.edu/cas // after // align exactly (or normalize): RP identifier set to https://app.example.edu/cas on both sides
Defensive patterns
Strategy: validation
Validate before calling
if (!credential.getAudience().equalsIgnoreCase(expectedAudience)) {
LOGGER.error("Audience mismatch: token={}, expected={}", credential.getAudience(), expectedAudience);
return;
} Type guard
boolean audienceMatches(WsFederationCredential c, String expected) {
return expected != null && expected.equalsIgnoreCase(c.getAudience());
} Prevention
- Keep wtrealm/relying-party ID identical on IdP and CAS sides; normalize trailing slashes.
- Pin the expected audience in configuration and version-control it per environment.
- Log both values on mismatch to speed diagnosis.
When it happens
Trigger: The wreply/wtrealm audience in the token from the IdP differs from expectedAudience passed by the validator, i.e. the token was issued for a different relying party identifier than CAS expects.
Common situations: Mismatch between the IdP's relying-party identifier (wtrealm) and CAS's configured entity/audience ID; trailing-slash differences; environment copied from another CAS instance; wrong realm selected at login.
Related errors
- Resource ID already exists in namespace .
- Username is null.
- Password is null.
- Password cannot be blank
- LDAP url cannot be empty/blank
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/d34f28ef89a256e4.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-wsfederation/src/main/java/org/apereo/cas/support/wsfederation/authentication/principal/WsFederationCredential.java:60
private ZonedDateTime notBefore;
private ZonedDateTime notOnOrAfter;
private ZonedDateTime retrievedOn;
private Map<String, List<Object>> attributes;
/**
* Validates the credential.
*
* @param expectedAudience the audience that the token was issued to (CAS Server)
* @param expectedIssuer the issuer of the token (the IdP)
* @param timeDrift the amount of acceptable time drift
* @return true if the credentials are valid, otherwise false
*/
public boolean isValid(final String expectedAudience, final String expectedIssuer, final long timeDrift) {
if (!this.audience.equalsIgnoreCase(expectedAudience)) {
LOGGER.warn("Audience [{}] is invalid where the expected audience should be [{}]", this.audience, expectedAudience);
return false;
}
if (!this.issuer.equalsIgnoreCase(expectedIssuer)) {
LOGGER.warn("Issuer [{}] is invalid since the expected issuer should be [{}]", this.issuer, expectedIssuer);
return false;
}
val retrievedOnTimeDrift = getRetrievedOn().minus(timeDrift, ChronoUnit.MILLIS);
if (getIssuedOn().isBefore(retrievedOnTimeDrift)) {
LOGGER.warn("Ticket is issued before the allowed drift. Issued on [{}] while allowed drift is [{}]",
this.issuedOn, retrievedOnTimeDrift);
return false;
}
val retrievedOnTimeAfterDrift = getRetrievedOn().plus(timeDrift, ChronoUnit.MILLIS);
if (getIssuedOn().isAfter(retrievedOnTimeAfterDrift)) {
LOGGER.warn("Ticket is issued after the allowed drift. Retrieved on [{}] and issued on [{}] while allowed drift is [{}]",
getRetrievedOn(), getIssuedOn(), retrievedOnTimeAfterDrift);
return false;
}View on GitHub (pinned to e7288fc434)