{"record":{"id":"79d5ded4c930e743","repo":"apereo/cas","slug":"sp-ssodescriptor-in-the-metadata-has-expired-at","errorCode":null,"errorMessage":"SP SSODescriptor in the metadata has expired at [{}]","messagePattern":"SP SSODescriptor in the metadata has expired at \\[(.+?)\\]","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-saml-idp-core/src/main/java/org/apereo/cas/support/saml/services/idp/metadata/SamlRegisteredServiceMetadataAdaptor.java","lineNumber":140,"sourceCode":"        } catch (final Exception e) {\n            LoggingUtils.error(LOGGER, e);\n        }\n        return Optional.empty();\n    }\n\n    private static Optional<SamlRegisteredServiceMetadataAdaptor> getAdaptor(\n        final String entityID,\n        final MetadataResolver chainingMetadataResolver,\n        final EntityDescriptor entityDescriptor) {\n        val ssoDescriptor = entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS);\n        if (ssoDescriptor != null) {\n            LOGGER.debug(\"Located SP SSODescriptor in metadata for [{}]. Metadata is valid until [{}]\", entityID,\n                ObjectUtils.getIfNull(ssoDescriptor.getValidUntil(), \"forever\"));\n            if (ssoDescriptor.getValidUntil() != null) {\n                val validUntil = DateTimeUtils.zonedDateTimeOf(ssoDescriptor.getValidUntil());\n                val expired = validUntil.isBefore(ZonedDateTime.now(ZoneOffset.UTC));\n                if (expired) {\n                    LOGGER.warn(\"SP SSODescriptor in the metadata has expired at [{}]\", ssoDescriptor.getValidUntil());\n                    return Optional.empty();\n                }\n            }\n            return Optional.of(new SamlRegisteredServiceMetadataAdaptor(ssoDescriptor,\n                entityDescriptor, chainingMetadataResolver));\n        }\n        LOGGER.warn(\"Could not locate SP SSODescriptor in the metadata for [{}]\", entityID);\n        return Optional.empty();\n    }\n\n    public ZonedDateTime getValidUntil() {\n        return DateTimeUtils.zonedDateTimeOf(this.ssoDescriptor.getValidUntil());\n    }\n\n    public Organization getOrganization() {\n        return this.ssoDescriptor.getOrganization();\n    }\n","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-saml-idp-core/src/main/java/org/apereo/cas/support/saml/services/idp/metadata/SamlRegisteredServiceMetadataAdaptor.java#L122-L158","documentation":"Both the EntityDescriptor and its SPSSODescriptor were located, but the SPSSODescriptor's own validUntil timestamp is before now (UTC). getAdaptor() logs a warning and returns Optional.empty(), refusing to hand back an adaptor whose endpoint/key validity window has closed.","triggerScenarios":"Calling get(entityID, ...) when the resolved SPSSODescriptor (entityDescriptor.getSPSSODescriptor(supportedNameIdFormat)) has a non-null validUntil earlier than ZonedDateTime.now(ZoneOffset.UTC).","commonSituations":"SP re-publishes metadata with short validity periods but the IdP cached the old document; a static metadata export was taken days ago and its descriptor-level validUntil has since passed; mismatch between entity-level (fine) and role-descriptor-level (expired) validity, which is easy to miss when eyeballing the XML.","solutions":["Refresh the metadata from the SP so the SPSSODescriptor carries a current validUntil, then reload it in CAS.","Inspect the XML: check <SPSSODescriptor validUntil=...> (not just <EntityDescriptor>) to see which level expired.","Switch from a snapshot file to the SP's live metadata URL so refreshes happen automatically.","Have the SP administrator republish metadata without a restrictive validUntil if the window is unnecessarily short.","Purge CAS's cached metadata for this service so the renewed document is picked up immediately."],"exampleFix":"// before (in SP metadata)\n// <SPSSODescriptor validUntil=\"2024-06-01T00:00:00Z\" ...>\n// after\n// <SPSSODescriptor validUntil=\"2030-01-01T00:00:00Z\" ...>  (or omit validUntil)","handlingStrategy":"validation","validationCode":"var descriptor = entityDescriptor != null ? entityDescriptor.getSPSSODescriptor(supportedNameIdFormat) : null;\nif (descriptor != null && descriptor.getValidUntil() != null) {\n    var validUntil = DateTimeUtils.zonedDateTimeOf(descriptor.getValidUntil());\n    if (validUntil.isBefore(ZonedDateTime.now(ZoneOffset.UTC))) {\n        throw new IllegalStateException(\"SPSSODescriptor expired at \" + validUntil\n            + \" — republish SP metadata\");\n    }\n}","typeGuard":"boolean ssoDescriptorCurrent(SPSSODescriptor sso) {\n    if (sso == null || sso.getValidUntil() == null) return true;\n    return DateTimeUtils.zonedDateTimeOf(sso.getValidUntil()).isAfter(ZonedDateTime.now(ZoneOffset.UTC));\n}","tryCatchPattern":"adaptorResolver.get(registeredService, entityID)\n    .filter(adaptor -> adaptor.getValidUntil() == null\n        || adaptor.getValidUntil().isAfter(ZonedDateTime.now(ZoneOffset.UTC)))\n    .orElseThrow(() -> new SamlException(\"SP SSODescriptor expired — refresh metadata for \" + entityID));","preventionTips":["Check validity at BOTH entity and SPSSODescriptor levels when reviewing metadata XML.","Have SP admins republish metadata on a schedule matching its declared validity window.","Prefer metadata without restrictive validUntil for long-lived internal integrations.","Inspect the adaptor's getValidUntil() in health checks to catch upcoming expiry."],"tags":["saml","metadata","expired","spssodescriptor"],"backgroundTag":"metadata-descriptor-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"}