Hi @tngan,
I want notify you something really helpful.
Summary
When IdP metadata (an <EntityDescriptor> containing <IDPSSODescriptor>) is passed to ServiceProvider({ metadata }), the instance is constructed successfully and entityMeta.getEntityID() returns a
value. There is no error, warning, or way to detect that the supplied metadata is for the wrong role. This makes it easy to silently mis-ingest IdP metadata in an SP-metadata upload flow.
Environment
samlify: 2.13.1
- Node: 24.x
- Schema validator:
@authenio/samlify-xsd-schema-validator
Steps to reproduce
import * as saml from 'samlify';
// This is IdP metadata (contains <IDPSSODescriptor>, no <SPSSODescriptor>)
const idpMetadataXml = `<?xml version="1.0"?>
<EntityDescriptor entityID="https://idp.example.com/metadata" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://idp.example.com/sso"/>
</IDPSSODescriptor>
</EntityDescriptor>`;
const sp = saml.ServiceProvider({ metadata: idpMetadataXml });
console.log(sp.entityMeta.getEntityID()); // "https://idp.example.com/metadata" ✅ returns
console.log(sp.entityMeta.getAssertionConsumerService('post')); // undefined
Expected
One of:
- Construction throws / surfaces a clear error when the supplied metadata does not contain an
SPSSODescriptor, or
- A way to introspect the role of parsed metadata (e.g.
getMetadataType() / hasSPSSODescriptor() / a documented note) so callers can validate input themselves.
Actual
-
ServiceProvider is built without error from IdP metadata.
getEntityID() returns a value (it reads the root <EntityDescriptor>, which is shared by both roles).
- SP-specific getters (
getAssertionConsumerService(...)) return undefined.
- No signal distinguishes "valid SP metadata with no ACS configured" from "wrong-role metadata supplied".
Root cause (from reading the source)
SpMetadata (build/src/metadata-sp.js) extracts via fixed localPaths targeting SPSSODescriptor only. On IdP metadata those paths match nothing → fields are undefined. The XML is well-formed, so
no parse error occurs.
IDPSSODescriptor extraction exists only in IdpMetadata (localPath: ['EntityDescriptor','IDPSSODescriptor', ...]); SpMetadata never inspects it.
getEntityID() is on the base Metadata class and reads the shared root, so it returns regardless of role.
- The XSD validator only checks SAML-metadata schema conformance — IdP metadata is valid SAML metadata, so it passes.
Proposed enhancement
A non-breaking option would be ideal, since some entities legitimately publish a single <EntityDescriptor> with both SPSSODescriptor and IDPSSODescriptor:
- Add a role-introspection helper on
Metadata/SpMetadata/IdpMetadata (e.g. getMetadataType() returning the descriptors present), and/or
- An opt-in strict flag (e.g.
ServiceProvider({ metadata, requireSPSSODescriptor: true })) that throws when the expected descriptor is absent.
Current workaround
Detect the descriptor manually before constructing the entity:
if (!xml.includes('SPSSODescriptor')) {
throw new Error(
xml.includes('IDPSSODescriptor')
? 'IdP metadata supplied — SP metadata required'
: 'Not SP metadata — SPSSODescriptor missing'
);
}
Hi @tngan,
I want notify you something really helpful.
Summary
When IdP metadata (an
<EntityDescriptor>containing<IDPSSODescriptor>) is passed toServiceProvider({ metadata }), the instance is constructed successfully andentityMeta.getEntityID()returns avalue. There is no error, warning, or way to detect that the supplied metadata is for the wrong role. This makes it easy to silently mis-ingest IdP metadata in an SP-metadata upload flow.
Environment
samlify: 2.13.1@authenio/samlify-xsd-schema-validatorSteps to reproduce
Expected
One of:
SPSSODescriptor, orgetMetadataType()/hasSPSSODescriptor()/ a documented note) so callers can validate input themselves.Actual
ServiceProvideris built without error from IdP metadata.getEntityID()returns a value (it reads the root<EntityDescriptor>, which is shared by both roles).getAssertionConsumerService(...)) returnundefined.Root cause (from reading the source)
SpMetadata(build/src/metadata-sp.js) extracts via fixedlocalPaths targetingSPSSODescriptoronly. On IdP metadata those paths match nothing → fields areundefined. The XML is well-formed, sono parse error occurs.
IDPSSODescriptorextraction exists only inIdpMetadata(localPath: ['EntityDescriptor','IDPSSODescriptor', ...]);SpMetadatanever inspects it.getEntityID()is on the baseMetadataclass and reads the shared root, so it returns regardless of role.Proposed enhancement
A non-breaking option would be ideal, since some entities legitimately publish a single
<EntityDescriptor>with bothSPSSODescriptorandIDPSSODescriptor:Metadata/SpMetadata/IdpMetadata(e.g.getMetadataType()returning the descriptors present), and/orServiceProvider({ metadata, requireSPSSODescriptor: true })) that throws when the expected descriptor is absent.Current workaround
Detect the descriptor manually before constructing the entity: