apereo/cas · warning
Could not locate SP SSODescriptor in the metadata for
Error message
Could not locate SP SSODescriptor in the metadata for [{}] What it means
The EntityDescriptor was resolved successfully, but it contains no SPSSODescriptor role (entityDescriptor.getSPSSODescriptor(...) returned null). getAdaptor() warns and returns Optional.empty() because CAS's IdP requires the SP role descriptor to obtain ACS locations, keys, and protocol support.
Solutions
- Verify the metadataLocation actually points to SP metadata containing <SPSSODescriptor>; if it points at an IdP document, replace it with the SP's metadata.
- Check supportedNameIdFormats: getSPSSODescriptor is looked up with the configured format — ensure the SP metadata's NameIDFormat is among the service's supported formats (an exact-format lookup can miss the descriptor).
- Regenerate/export complete SP metadata including the SPSSODescriptor with its ACS and keys.
- Validate the metadata XML with an XMLOnline validator or OpenSAML tooling to confirm the role descriptor is present.
- If the SP only supports another role, register it with the appropriate CAS service type instead of the SAML SP flow.
Example fix
// before // metadataLocation: https://idp.example.org/saml/metadata (IdP metadata, no SPSSODescriptor) // after // metadataLocation: https://sp.example.org/saml/sp/metadata (contains <SPSSODescriptor>)
Defensive patterns
Strategy: validation
Validate before calling
var resolver = ...; // resolved chaining resolver
var descriptor = resolver.resolveSingle(new CriteriaSet(new EntityIdCriterion(entityID)));
boolean hasSpRole = descriptor != null
&& descriptor.getSPSSODescriptor(supportedNameIdFormat) != null;
if (!hasSpRole) {
throw new IllegalStateException("Metadata for " + entityID
+ " has no SPSSODescriptor (with format " + supportedNameIdFormat + ") — is this SP metadata?");
} Type guard
boolean isSpMetadata(EntityDescriptor d, String nameIdFormat) {
return d != null && d.getSPSSODescriptor(nameIdFormat) != null;
} Try / catch
return adaptorResolver.get(registeredService, entityID)
.map(Optional::of)
.orElseGet(() -> {
log.warn("Entity {} lacks SPSSODescriptor; verify metadataLocation points at SP metadata", entityID);
return Optional.empty();
}); Prevention
- Confirm the metadataLocation URL returns SP (not IdP) metadata before registering the service.
- Validate metadata structure with OpenSAML tooling, not just well-formedness.
- Ensure the service's supported NameID formats overlap with the SP metadata's NameIDFormat values.
- Never hand-trim metadata exports; always use the vendor's full-document export.
When it happens
Trigger: Calling get(entityID, ...) where the metadata for the entity defines an IdPSSODescriptor/AttributeAuthorityDescriptor or nothing at all, but no <SPSSODescriptor> — e.g. the metadataLocation points to an IdP's own metadata instead of the SP's.
Common situations: Accidentally pointing the CAS SAML service's metadataLocation at an IdP metadata document; metadata for a non-SSO role only (e.g., attribute authority); hand-edited/truncated metadata missing the SPSSODescriptor element; entity is an ECP-only or discovery-registered entity without SP SSO support.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- SP SSODescriptor in the metadata has expired at
- No assertion consumer service could be found for entity
- Endpoint for is not available or does not define a binding…
- Endpoint for does not define a binding or location for…
- Metadata directory location cannot be located/created
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/18a985ecbbd98d47.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-saml-idp-core/src/main/java/org/apereo/cas/support/saml/services/idp/metadata/SamlRegisteredServiceMetadataAdaptor.java:147
final String entityID,
final MetadataResolver chainingMetadataResolver,
final EntityDescriptor entityDescriptor) {
val ssoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS);
if (ssoDescriptor != null) {
LOGGER.debug("Located SP SSODescriptor in metadata for [{}]. Metadata is valid until [{}]", entityID,
ObjectUtils.getIfNull(ssoDescriptor.getValidUntil(), "forever"));
if (ssoDescriptor.getValidUntil() != null) {
val validUntil = DateTimeUtils.zonedDateTimeOf(ssoDescriptor.getValidUntil());
val expired = validUntil.isBefore(ZonedDateTime.now(ZoneOffset.UTC));
if (expired) {
LOGGER.warn("SP SSODescriptor in the metadata has expired at [{}]", ssoDescriptor.getValidUntil());
return Optional.empty();
}
}
return Optional.of(new SamlRegisteredServiceMetadataAdaptor(ssoDescriptor,
entityDescriptor, chainingMetadataResolver));
}
LOGGER.warn("Could not locate SP SSODescriptor in the metadata for [{}]", entityID);
return Optional.empty();
}
public ZonedDateTime getValidUntil() {
return DateTimeUtils.zonedDateTimeOf(this.ssoDescriptor.getValidUntil());
}
public Organization getOrganization() {
return this.ssoDescriptor.getOrganization();
}
public Signature getSignature() {
return this.ssoDescriptor.getSignature();
}
public List<ContactPerson> getContactPersons() {
return this.ssoDescriptor.getContactPersons();
}View on GitHub (pinned to e7288fc434)