spring-projects/spring-security · error · Saml2Exception

Metadata response is missing a SingleSignOnService, necessar

Error message

Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests

What it means

Spring Security SAML2 throws this Saml2Exception when the parsed IdP metadata has an IDPSSODescriptor but no SingleSignOnService entries. The SingleSignOnService location/binding is what the application uses to send AuthnRequest messages; without it the RelyingPartyRegistration cannot be constructed.

Source

Thrown at saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyDetails.java:130

		if (verification.isEmpty()) {
			throw new Saml2Exception(
					"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
		}
		String entityId = entity.getEntityID();
		Assert.notNull(entityId, "EntityDescriptor#EntityID cannot be null");
		OpenSamlAssertingPartyDetails.Builder builder = new OpenSamlAssertingPartyDetails.Builder(entity)
			.entityId(entityId)
			.wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
			.verificationX509Credentials((c) -> c.addAll(verification))
			.encryptionX509Credentials((c) -> c.addAll(encryption));

		List<SigningMethod> signingMethods = signingMethods(idpssoDescriptor);
		for (SigningMethod method : signingMethods) {
			Assert.notNull(method.getAlgorithm(), "EntityDescriptor declares a SigningMethod with no value");
			builder.signingAlgorithms((algorithms) -> algorithms.add(method.getAlgorithm()));
		}
		if (idpssoDescriptor.getSingleSignOnServices().isEmpty()) {
			throw new Saml2Exception(
					"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
		}
		for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) {
			Saml2MessageBinding binding;
			if (Saml2MessageBinding.POST.getUrn().equals(singleSignOnService.getBinding())) {
				binding = Saml2MessageBinding.POST;
			}
			else if (Saml2MessageBinding.REDIRECT.getUrn().equals(singleSignOnService.getBinding())) {
				binding = Saml2MessageBinding.REDIRECT;
			}
			else {
				continue;
			}
			String location = singleSignOnService.getLocation();
			Assert.notNull(location, "EntityDescriptor has a SingleSignOnService declaration, but no Location");
			builder.singleSignOnServiceLocation(location).singleSignOnServiceBinding(binding);
			break;
		}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Use the IdP's official metadata URL/file that includes <md:SingleSignOnService Binding="...urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="..."/> and re-import
  2. Manually set it via assertingParty(details -> details.singleSignOnServiceLocation("https://idp/sso").singleSignOnServiceBinding(Saml2MessageBinding.POST))
  3. Confirm the metadata document is IdP metadata (IDPSSODescriptor), not SP metadata
  4. If metadata is generated, fix the generator to emit at least one SingleSignOnService

Example fix

// before (metadata lacks SSO service -> error)
RelyingPartyRegistration r = RelyingPartyRegistrations.fromMetadataLocation("https://idp/meta").build();
// after: declare SSO service manually
RelyingPartyRegistration r = RelyingPartyRegistration.withAssertingPartyMetadata(party -> party
    .entityId("https://idp.example.com/sso")
    .singleSignOnServiceLocation("https://idp.example.com/sso/SSO")
    .singleSignOnServiceBinding(Saml2MessageBinding.POST))
    .registrationId("idp")
    .build();
Defensive patterns

Strategy: validation

Validate before calling

Document doc = parseXml(metadataBytes);
NodeList sso = doc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:metadata", "SingleSignOnService");
if (sso.getLength() == 0) throw new IllegalArgumentException("Metadata has no SingleSignOnService");

Try / catch

try {
    return RelyingPartyRegistrations.fromMetadataLocation(location).build();
} catch (Saml2Exception ex) {
    // supply SSO location manually from IdP admin console values
    return builderWithManualSsoEndpoint(ex);
}

Prevention

When it happens

Trigger: RelyingPartyRegistrations.fromMetadata/fromMetadataLocation or OpenSamlAssertingPartyDetails.withEntityDescriptor on metadata where idpssoDescriptor.getSingleSignOnServices() is empty.

Common situations: Hand-crafted metadata containing only certificates and entityId; IdP published metadata limited to SLO/logout services; truncated or partially downloaded metadata; wrong entity (e.g. SP metadata fed as IdP metadata).

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/cd4f955437919537. Report an issue: GitHub.