Importing client from SAML 2.0 Service Provider Metadata XML fails (Keycloak 22)

Hi everyone,
Our project uses Keycloak 8.0.1 and supports usage of an external identity provider (IdP). For testing purposes we use another Keycloak 8.0.1 instance as an external SAML IdP.

At the client side (1st Keycloak instance) we create a new SAML IdP and export it via Admin Console→Identity Providers→idp→Export tab→Download. The created XML file is then imported at the external IdP side (2nd Keycloak instance) via Admin Console→Clients→Create→Import to create a SAML client respectively. Configuring IdP and client that way worked successfully for Keycloak 8.0.1.

After we switching to Keycloak to 22.0.5, the step of creating a SAML client on the external IdP side (2nd Keycloak) by importing an IdP XML failed with this error in logs:

ParsingException
2024-01-30 16:46:24,279 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-334) Uncaught server error: java.lang.RuntimeException: ParsingException [location=null]org.keycloak.saml.common.exceptions.ParsingException: PL00063: Parser: Required attribute missing: Name
        at org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter.loadEntityDescriptors(EntityDescriptorDescriptionConverter.java:156)
        at org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter.convertToInternal(EntityDescriptorDescriptionConverter.java:73)
        at org.keycloak.services.resources.admin.RealmAdminResource.lambda$convertClientDescription$1(RealmAdminResource.java:174)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
        at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
        at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
        at java.base/java.util.HashMap$ValueSpliterator.tryAdvance(HashMap.java:1800)
        at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:129)
        at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:527)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:150)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
        at org.keycloak.services.resources.admin.RealmAdminResource.convertClientDescription(RealmAdminResource.java:175)
		...
		Caused by: ParsingException [location=null]org.keycloak.saml.common.exceptions.ParsingException: PL00063: Parser: Required attribute missing: Name
        at org.keycloak.saml.common.DefaultPicketLinkLogger.parserRequiredAttribute(DefaultPicketLinkLogger.java:370)
        at org.keycloak.saml.common.util.StaxParserUtil.getRequiredAttributeValue(StaxParserUtil.java:429)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLRequestedAttributeParser.instantiateElement(SAMLRequestedAttributeParser.java:47)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLRequestedAttributeParser.instantiateElement(SAMLRequestedAttributeParser.java:33)
        at org.keycloak.saml.common.parsers.AbstractStaxParser.parse(AbstractStaxParser.java:56)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLAttributeConsumingServiceParser.processSubElement(SAMLAttributeConsumingServiceParser.java:57)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLAttributeConsumingServiceParser.processSubElement(SAMLAttributeConsumingServiceParser.java:17)
        at org.keycloak.saml.common.parsers.AbstractStaxParser.parse(AbstractStaxParser.java:97)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLSPSSODescriptorParser.processSubElement(SAMLSPSSODescriptorParser.java:58)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLSPSSODescriptorParser.processSubElement(SAMLSPSSODescriptorParser.java:16)
        at org.keycloak.saml.common.parsers.AbstractStaxParser.parse(AbstractStaxParser.java:97)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLEntityDescriptorParser.processSubElement(SAMLEntityDescriptorParser.java:87)
        at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLEntityDescriptorParser.processSubElement(SAMLEntityDescriptorParser.java:40)
        at org.keycloak.saml.common.parsers.AbstractStaxParser.parse(AbstractStaxParser.java:97)
        at org.keycloak.saml.processing.core.parsers.saml.SAMLParser.parse(SAMLParser.java:123)
        at org.keycloak.saml.common.parsers.AbstractParser.parse(AbstractParser.java:93)
        at org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter.loadEntityDescriptors(EntityDescriptorDescriptionConverter.java:154)
        ... 57 more

After comparing the export XML files of SAML IdPs produced by Keycloak 8 and Keycloak 22, it turned out that in version 22 the output XML additionally contained the definitions of SAML mappers configured in IdP.

IdP XML Keycloak 8

Keycloak 8 XML
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://192.168.17.30/auth/realms/default">
	<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext">
		<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://192.168.17.30/auth/realms/default/broker/saml-17.59/endpoint"/>
		<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
		<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://192.168.17.30/auth/realms/default/broker/saml-17.59/endpoint" index="1" isDefault="true"/>
	</SPSSODescriptor>
</EntityDescriptor>

IdP XML Keycloak 22

Keycloak 22 XML
<md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://192.168.17.14/auth/realms/default" ID="ID_6d6ee61a-4079-4835-b4aa-48fa0e88ea12">
	<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="false" WantAssertionsSigned="false">
		<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://192.168.17.14/auth/realms/default/broker/saml/endpoint"/>
		<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
		<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://192.168.17.14/auth/realms/default/broker/saml/endpoint" isDefault="true" index="1"/>
		<md:AttributeConsumingService isDefault="true" index="0">
			<md:ServiceName xml:lang="en">Project Foo Service</md:ServiceName>
			<md:RequestedAttribute FriendlyName="surname" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"/>
		</md:AttributeConsumingService>
	</md:SPSSODescriptor>
</md:EntityDescriptor>

Looking at org.keycloak.saml.processing.core.parsers.saml.metadata.SAMLRequestedAttributeParser#instantiateElement it turns out that any element in the import IdP XML must have ‘Name’ attribute filled in, otherwise import fails.

Keycloak 8 did not include IdP mappers to IdP output XML and the XML was successfully imported when configuring a SAML client.

Keycloak 22 started including IdP mappers to the output XML. If you configure a mapper (e.g. Attribute importer) in SAML IdP, fill in only ‘Friendly name’ mapper field (leaving ‘Attribute name’ field empty) and export the IdP as XML, it will not be possible to import the exported XML to create a SAML client at the IdP side.

I have 2 questions regarding Keycloak 22.0.5:

  1. Is it expected that IdP mappers are now also exported to XML as part of IdP?
  2. Is it expected that IdP mappers must be created with the ‘Attribute name’ filled in to be succesfully imported afterwards (e.g. when creating a SAML client by importing IdP XML file)?

Thanks in advance,
Volodymyr