SAML Proxying EntraID / Azure with the Shibboleth IdP



Please read and follow the v5 SAML AuthN documentation first, before or along with using this example.

This documentation is not maintained by the development team and may not be entirely accurate or consistent with the software at any given time. It is a complement to the documentation, not a replacement for it.


Overview 

The Shibboleth IdP can be a proxy to EntraID leveraging its features while keeping in alignment to R&E federation's multi-lateral trust model. The steps can be applied to any V5+ base install and are platform neutral. When done the IdP will be capable of being a full pass-through proxy with Attributes originating from EntraID as well as the ability to take advantage of MFA and REFEDS-MFA authentication contexts.

Expected Audience

The configuration steps assume some familiarity with the Shibboleth IdP, its layout, and being familiar with software installation and sustainment. 

Where to Install

This configuration can be applied to an IdP on-premises (on-prem) or in the cloud. The location of the IdP may be influenced by the location of your data. Unless all your data is kept in EntraID you will likely be implementing a hybrid approach for attributes from both EntraID and normal Shibboleth IdP attribute resolution.


Acknowledgements: Thanks to contributors  from JISC, other US higher ed institutions, the Shibboleth team, and others for guidance and input on the material.

IdP Proxying Concepts: Appearances and Perspectives

Proxying techniques are confusing at times and helpful to understand the perspectives:

EntraIDs:

  • The Shibboleth IdP will look like a Relying Party to EntraID
  • It will be a single non-Gallery App registered in the Enterprise Apps portal for Azure
    • means that all conditional access and policy rules are applied at an IdP-wide level, NOT per Shibboleth Relying Party.
    • also means that applying rules for MFA as controlled by EntraID are for the entire IdP and NOT discrete Shibboleth Relying Parties.

Relying Party (Service Provider) from the multi-lateral trust/R&E federation community:

  • It will look like a fully functional multi-lateral capable IdP 
  • It will not require any changes in behaviour to the SP
  • Attribute asserted over the wire are what the SP expects. i.e. attributes are NOT seen as 'claims' 

Users: 

  • A user will sign into EntraID
  • The Shibboleth IdP as proxy will redirect as necessary
  • The user will have two sessions:
    • one with EntraID
    • one with the Shibboleth IdP

Federation Operator:

  • The entity registered in the R&E federation is the Shibboleth IdP, built for and by the R&E community and should meet all technical requirements as does any other Shibboleth IdP
  • Alignment to any federation baseline expectations is not just software but also how it's operated and managed and is in the hands of the IdP operator like any other IdP. 

IdP Proxying Concepts: What a Proxy Flow Looks Like

When the user initiates sign-on the following usually happens:

    1. An Authentication request arrives at Shibboleth IdP
    2. The Shibboleth IdP observes it's configured for SAML sign on and redirects to an upstream IdP (EntraID). Settings for this are controlled in

      1. idp.properties
      2. authn/authn.properties
    3. User Authenticates or has an existing session at upstream IdP (EntraID)
    4. The upstream IdP (EntraID) constructs a SAML Assertion with one or more attributes and is sent with the user to the Shibboleth IdP 
    5. The Shibboleth IdP receives the assertion and:
      1. verifies that the assertion arrives from an entity it trusts as configured in metadata-providers.xml

      2. filters the assertion according to rules in attribute-filter.xml
      3. Extracts the real user as configured in attribute-based subject c14n.
        1. Looks through list of AttributesToResolve in the resolver, resolves each one.
        2. Applies a idp admin configured regular expression  to the EntraID name claim
      4. Resulting trusted real username is used as $resolutionContext.principal (eg. existing LDAP data connector, etc.) during "standard" attribute resolution process

Implementing the Solution

Prerequisites

Be sure to have these assets in place and appropriate access to them before you start:

  1. A working Shibboleth Identity Provider at V5 or above running somewhere.
  2. An active EntraID tenant that you have administrative control in
  3. The ability in EntraID to create an enterprise Non-Gallery SAML App
  4. A suitable attribute available to both IdPs to use as a "joining" attribute used in local lookups if necessary.

Terms and Settings 

This document refers to some key settings and terms described here. For your installation have these on hand to speed up configuration:

  • Your original IdP EntityID: https://idp.example.com/idp
  • Your Upstream IdP EntityID which is EntraID Identifier: https://sts.windows.net/<sts_tenant_id>/ (Finding your tenantID)
  • The Proxy Joining attribute common to both services:
    • SamAccountName is often a good choice and appears as 'name' in claims.
      • will exist as a 'scoped' username
      • the scope being that of the domain used on-premises and is usually internet friendly (not something.local or something.int)
  • Relying Party/Service Provider Microsoft will use "Relying Party" at times to mean "Service Provider"

Steps and Tasks

Step 1. Configuring Trust Between EntraID and the Shibboleth IdP

There are 3 main trust-related configurations to adjust:

  • In the Shibboleth IdP:
    1. to have the SPSSODescriptor present in its metadata for when metadata for the IdP is fetched by URL
    2.  to have a metadata entry for the EntraID IdP in its metadata providers' configuration
  • In EntraID

3. the EntraID trust to the Shibboleth IdP as a Relying Party

Trust Task: 1. Update your IdP's metadata

As your IdP will need act as an SP, you'll need extra blocks in your entity's metadata.

  • Update your existing idp-metadata.xml file to include a <SPSSODescriptor> block. 
  • Copy the signing and encryption certificates from the IdP part of the metadata and replace the base URI used below as an example (https://idp.example.com/idp) with the base of your IdP.

This SPSSODescriptor enriched metadata is used with your upstream IDP (EntraID), not in your R&E federation and not necessary to be published in the R&E fed.

<EntityDescriptor entityID="https://idp.example.com/idp" ...>

    <!-- Already present IdP data -->
    <IDPSSODescriptor ...>
        ...
    </IDPSSODescriptor>

    <AttributeAuthorityDescriptor>
        ...
    </AttributeAuthorityDescriptor>


    <!-- New SP block -->
    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">

        <KeyDescriptor use="signing">
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>
                    ...Signing Certificate from IdP...
                    </ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </KeyDescriptor>
        <KeyDescriptor use="encryption">
            <ds:KeyInfo>
                <ds:X509Data>
                    <ds:X509Certificate>
                    ...Encryption Certificate from IdP...
                    </ds:X509Certificate>
                </ds:X509Data>
            </ds:KeyInfo>
        </KeyDescriptor>

    <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://idp.example.com/idp/profile/Authn/SAML2/POST/SSO" index="0"/>
</SPSSODescriptor>

</EntityDescriptor>

Trust Task: 2. Register your IdP with EntraID

An EntraID Enterprise Application needs to be created of type 'Non-Gallery Application' and configured for SAML.

Steps to create the app:

  • sign in to the EntraID Portal > choose Enterprise Applications > Create New Application > Non Gallery Application > Actually create one > select SAML Sign On > select SAML Sign  On settings

Once created edit the Basic SAML Configuration Section where you will use two things:

  • the EntityID: https://idp.example.com/idp/shibboleth (same as for your IdP )
  • the Assertion Consumer Service (ACS) URL: https://idp.example.com/idp/profile/Authn/SAML2/POST/SSO

Other items like Logout URL can also be provided but the Shibboleth side will not handle logout in this case, so this is best omitted.


Keep this Relying Party editing environment open for the next step.

Trust Task: 3. Register the upstream IdP's metadata locally

Your Shibboleth IdP doesn't know of the EntraID IdP so you need to register it locally. This is handled just like any other metadata and can be included in your metadata-providers.xml as a new <MetadataProvider> element. For example:

<MetadataProvider id="AzureAD-idp-metadata"
        xsi:type="FilesystemMetadataProvider"
        metadataFile="/opt/idp/metadata/AzureAD-idp.xml" />

The file AzureAD-idp.xml must contain valid SAML 2. Metadata for your tenant's entity containing:

  • an IDPSSODescriptor block containing a hand entered shibmd:scope element
  • an X509Descriptor element with use=signing
  • 2 SingleSignOnService elements

  • 1 SingleLogOut  element (not really supported but will allow error responses to be returned to Azure)

This can be most easily found and downloaded by clicking on 'Federation Metadata XML Download' in Section 3 of the EntraID Relying Party information, however it needs to be scrubbed a bit to produce a lean version of the metadata.

Hint: Those on mac or unix may find it convenient processing the file through xmllint --format downloadedfile.xml > downloadedfile-formated.xml
(xmllint
can be found in a package named libxml2 on RedHat or libxml2-utils on Debian/Ubuntu.)

Hand-adjust the downloaded metadata to add both the XML NameSpace and a value for shibmd:Scope in the IDPSSODescriptor element:

  1. Add to the EntityDescriptor in your downloaded metadata: xmlns:shibmd="urn:mace:shibboleth:metadata:1.0
  2. add the Extensions element inside the IDPSSODescriptor element to align with the scope validation per the rule in Proxy Task 2 below:
<Extensions>
  <shibmd:Scope regexp="false">your_scope_domain_here</shibmd:Scope>
</Extensions>

When done, add an entry to metadata-providers.xml.  If you fetch this entry elsewhere or by other means (e.g. curl or otherwise)  ensure you know and understand its pedigree and don't blindly trust it without some diligence of some sort.


Note!

  • About certificates:
    • The certificate generated by EntraID is only 3 years in duration and is the trust between Azure and the Shibboleth Proxy only.
    • Expect EntraID to cease trust when it expires.
    • Also expect certificate rollover between EntraID and your IdP instance to be handled by the IdP admin team. 
  • About the metadata download  itself:
    • There are no linefeeds between lines (so reformat e.g. with xmllint as shown above, if desired)
    • It contains unnecessary elements that can be removed to reduce the size of the file:
      • a signature embedded in the top of the file
      • The section Fed:ClaimTypesOffered 

Trust Task: 4. Configure EntraID Attribute Release to the Shibboleth IdP

While still in the edit mode of the Relying Party, configure the necessary attributes needed for the Shibboleth IdP operation.

  • For a pass-through proxy approach
    • All attributes originate from EntraID and must be released to this Relying Party for the Shibboleth IdP to marshall downstream
  • For a hybrid proxy approach
    • Technically the single attribute you want to use for user lookup is required.


user.localuserprincipalname is the usual choice for the main user identifier but review your practices carefully. This identifier is ALWAYS in the realm of the EntraID tenant whereas user.userprincipalname may not be for guest users in the EntraID tenant and WILL NOT pass the shib:md scope in your metadata. 

Step 2. Configure the IdP for Proxing Behaviour

The following changes are needed to adjust the IdP to proxying behaviour:

  1. Changing the authentication flow to SAML authentication
  2. Configuring which Entity to delegate to for the flow
  3. Update attribute filter to allow incoming attributes to be ingested
  4. Set up attribute extraction through Subject Canonicalisation (c14n and resolver)

Proxy Task 1. Change the IdP authentication flow to SAML

There are three steps for this task that embody some of the existing Authentication Configuration and  SAMLAuthnConfiguration guidance:

  1. Enable the flow by updating idp.authn.flows in authn/authn.properties:

    idp.authn.flows=SAML

2. Enable to whom to delegate authentication by updating idp.authn.SAML.proxyEntityID in the same file authn/authn.properties:

     idp.authn.SAML.proxyEntityID = https://sts.windows.net/00BC123-TENANTID-INCLUDING-TRAILING-SLASH/

Proxy Task 2. Update Your Attribute Filter

The IdP will not ingest attributes from the EntraID Upstream IdP unless they're allowed in by a filter.

  1. Add a new <AttributeFilterPolicy> to permit trusted attributes to be handled.
  2. REPLACE sts_tenant_id with your identifier. NOTE THE TRAILING SLASH TO BE INCLUDED

Note:  There is a policy rule ScopeMatchesShibMDScope in effect on the attribute 'name' (known internally as azureName).

This will enforce that the idp.scope set in conf/idp.properties agrees with the EntraID field to avoid any oddities. If they disagree, this rule will fail-closed and not sign the user in.

Be sure the IdP metadata for the your EntraID IdP has the right scope for things to work smoothly

<AttributeFilterPolicy id="FilterPolicyObject-Proxy-FromAzure-byIssuer-Type">
    <PolicyRequirementRule xsi:type="Issuer" value="https://sts.windows.net/00BC123-TENANTID-INCLUDING-TRAILING-SLASH/" />
         
    <AttributeRule attributeID="azureDisplayname" permitAny="true" />
    <AttributeRule attributeID="azureGivenname" permitAny="true" />
    <AttributeRule attributeID="azureSurname" permitAny="true" />
    <AttributeRule attributeID="azureAuthnmethodsreferences" permitAny="true" />
    <AttributeRule attributeID="azureIdentityprovider" permitAny="true" />
    <AttributeRule attributeID="azureTenantid" permitAny="true" />
    <AttributeRule attributeID="azureEmailaddress" permitAny="true" />
    <AttributeRule attributeID="azureObjectidentifier" permitAny="true" />
    <AttributeRule attributeID="azureGroups" permitAny="true" />
    <AttributeRule attributeID="azureName">
        <PermitValueRule xsi:type="ScopeMatchesShibMDScope" />
    </AttributeRule>
</AttributeFilterPolicy>

Proxy Task 3. Enable IdP to Recognize EntraID Claims 

EntraID issues SAML assertions, however they are presented in an Azure/WS-Fed-centric naming convention (as "claims").

Our technique is to add in a new attribute mapping file that we can then use to parse the claims into internal attributes and in turn use them in the attribute resolver.

  1. Add a new file called in the IdP as attributes/azureClaims.xml if it doesn't already exist. This file contains each of your attributes from EntraID that you want to use in the resolver.

The sample below is not exhaustive but has enough to get started on the claims file.

  • For azName we apply a scoped rule for safety considerations. The scope value MUST match the <shibmd:Scope> extension in the upstream IdP metadata.
  • The "saml2.nameFormat" rule property is urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified as the claims don't carry the proper format on them.


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
                           
       default-init-method="initialize"
       default-destroy-method="destroy">

    <bean parent="shibboleth.TranscodingRuleLoader">
    <constructor-arg>
    <list>
        <!-- claims relevant to person record -->
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureName</prop>
                    <prop key="transcoder">SAML2ScopedStringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Name</prop>
                    <prop key="description.en">Azure UPN of an account expected to be scoped thus transcoded that way</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureEmailaddress</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Mail</prop>
                    <prop key="description.en">Azure emailaddress field of an account</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureDisplayname</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.microsoft.com/identity/claims/displayname</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Mail</prop>
                    <prop key="description.en">Azure displayname field of an account</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureGivenname</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Given name</prop>
                    <prop key="description.en">Azure given name of an account</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureSurname</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Surname</prop>
                    <prop key="description.en">Azure surname of an account</prop>
                </props>
            </property>
        </bean>

        <!-- default claims from Azure for any entity -->
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureTenantid</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.microsoft.com/identity/claims/tenantid</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Tenant ID</prop>
                    <prop key="description.en">Azure tenantid</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureObjectidentifier</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.microsoft.com/identity/claims/objectidentifier</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Object Identifier</prop>
                    <prop key="description.en">Azure object identifier of an account</prop>
                </props>
            </property>
        </bean>
         <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureIdentityprovider</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.microsoft.com/identity/claims/identityprovider</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Identity Provider entityid</prop>
                    <prop key="description.en">Azure entityID of the tenant in Azure</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureAuthnmethodsreferences</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.microsoft.com/claims/authnmethodsreferences</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Authnmethodsreferences</prop>
                    <prop key="description.en">Azure authentication method (password, modern auth? etc)</prop>
                </props>
            </property>
        </bean>
        <bean parent="shibboleth.TranscodingProperties">
            <property name="properties">
                <props merge="true">
                    <prop key="id">azureGroups</prop>
                    <prop key="transcoder">SAML2StringTranscoder</prop>
                    <prop key="saml2.name">http://schemas.microsoft.com/ws/2008/06/identity/claims/groups</prop>
                    <prop key="saml2.nameFormat">urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</prop>
                    <prop key="displayName.en">Groups</prop>
                    <prop key="description.en">Azure Group Membership</prop>
                </props>
            </property>
        </bean>



    </list>
    </constructor-arg>
    </bean>
    
</beans>


2. Update attributes/default-rules.xml to include this new file as a reference by adding this line to it after the last import:

<import resource="azureClaims.xml" />

3. Update attribute-resolver.xml to reveal our new attributes from EntraID by adding a new DataConnector:

 <DataConnector id="passthroughAttributes" xsi:type="Subject" 
    exportAttributes="azureName azureEmailaddress azureDisplayname azureGivenname azureSurname azureTenantid azureObjectidentifier azureIdentityprovider azureAuthnmethodsreferences" />

Now that we have the the IdP able to interpret claims from EntraID, we need to turn our attention to using them for user canonicalization and for populating our attributes.

Proxy Task 4. Subject Canonicalisation

Much simplified, this step extracts the canonical (authoritative/normalized) username during a SAML proxying flow, and uses the subject-c14n.properties and subject-c14n.xml to guide the IdP on what to do.

The full reference is at  SAMLAuthnConfiguration#Attribute-Sourced-C14N-Example with specifics mentioned below for Azure/EntraID:

1. update conf/c14n/subject-c14n.properties  with the following to guide what the IdP should extract and inputs to the process:

idp.c14n.attribute.attributesToResolve = azureName,azureEmailaddress,azureDisplayname,azureGivenname,azureSurname,azureTenantid,azureObjectidentifier,azureIdentityprovider,azureAuthnmethodsreferences
#  and then select a principal name from
idp.c14n.attribute.attributeSourceIds = azureName
# Allows direct use of attributes via SAML proxy authn, bypasses resolver
idp.c14n.attribute.resolveFromSubject = true
idp.c14n.attribute.resolutionCondition = shibboleth.Conditions.FALSE
#
# Specific to this KB article, this is the regex used in the next step for subject-c14n.xml
idp.c14.saml.proxy.regex.for.principal=^(.+)@yourdomain\.edu$

2. Review and ensure the bean in c14n/subject-c14n.xml has been uncommented (near the top of the file):

<bean id="c14n/attribute" parent="shibboleth.PostLoginSubjectCanonicalizationFlow" /> 


3. Add to the same, c14n/subject-c14n.xml at the bottom of the file just before the close bean teag.

This is to apply an Attribute Transformref:AttributePostLoginC14NConfiguration#Reference) to  the idp.c14n.attribute.attributeSourceIds with the regex from idp.c14.saml.proxy.regex.for.principal:

<util:list id="shibboleth.c14n.attribute.Transforms">	  
       <bean parent="shibboleth.Pair" p:first="%{idp.c14.saml.proxy.regex.for.principal}" p:second="$1" />
    </util:list>


At this point you should have a valid, locally understood username to pass into the "normal" resolver that already works, usually available as: $resolutionContext.principal

Proxy Task 5. Configuring Attribute pass-through and/or hybrid resolving

Attributes originating from EntraID are referenced in attribute-resolver.xml through a  SubjectDataConnector  and then referencing them in the AttributeDefinition

Be sure to comment out the old definition of the names in the attribute-resolver.xml or you may get unexpected results.

	<AttributeDefinition xsi:type="Simple" id="userPrincipalName">
        <InputDataConnector ref="passthroughAttributes" attributeNames="azureName" />
    </AttributeDefinition>


	<AttributeDefinition xsi:type="Simple" id="sn">
        <InputDataConnector ref="passthroughAttributes" attributeNames="azureSurname" />
    </AttributeDefinition>
	
<AttributeDefinition xsi:type="Simple" id="givenName">
        <InputDataConnector ref="passthroughAttributes" attributeNames="azureGivenname" />
    </AttributeDefinition>

<AttributeDefinition xsi:type="Simple" id="mail">
        <InputDataConnector ref="passthroughAttributes" attributeNames="azureEmailaddress" />
    </AttributeDefinition>

<AttributeDefinition xsi:type="Simple" id="displayName">
        <InputDataConnector ref="passthroughAttributes" attributeNames="azureDisplayname" />
    </AttributeDefinition>

<AttributeDefinition xsi:type="Simple" id="cn">
        <InputDataConnector ref="passthroughAttributes" attributeNames="azureDisplayname" />
    </AttributeDefinition>

<DataConnector id="passthroughAttributes" xsi:type="Subject"
   exportAttributes="azureName azureEmailaddress azureDisplayname azureGivenname azureSurname azureTenantid azureObjectidentifier azureIdentityprovider azureAuthnmethodsreferences azureGroups" />

Proxy Task 6. Handling REFEDS AuthnContext Requests (optional)

Supporting the REFEDS-MFA AuthenticationContext can be handled by instructing the Shibboleth IdP to transform inbound and outbound SAML2 Authentication Contexts during the proxying process.

To do this, add the following XML fragment for shibboleth.PrincipalProxyRequestMappings and shibboleth.PrincipalProxyResponseMappings  in authn/authn-comparison.xml.

This transformation will obligate an Azure/EntraID sign-on to require multiple authentication methods to satisfy the authenticationContext.  More details can be found in AuthenticationConfiguration#Advanced-Topics

<util:map id="shibboleth.PrincipalProxyRequestMappings">
	<entry>
		 <key>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
                  c:classRef="https://refeds.org/profile/mfa" />
		</key>
		<list>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
                  c:classRef="http://schemas.microsoft.com/claims/multipleauthn" />
		</list>
	</entry>
</util:map>
<util:map id="shibboleth.PrincipalProxyResponseMappings">
	<entry>
		 <key>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
                          c:classRef="http://schemas.microsoft.com/claims/multipleauthn" />
		</key>
		<list>
			<bean parent="shibboleth.SAML2AuthnContextClassRef"
                          c:classRef="https://refeds.org/profile/mfa" />
		</list>
	</entry>
</util:map>

Update the support for the SAML authentication flow to understand the REFEDS MFA profile

Since v4.1+ this is now controlled in the file authn/authn.properties so it understands the REFEDS MFA profile by adding a supportedPrinciples property in the SAML section:

authn/authn.properties update to support REFEDS MFA
idp.authn.SAML.supportedPrincipals = \
    saml2/https://refeds.org/profile/mfa, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport, \
    saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Password, \
    saml1/urn:oasis:names:tc:SAML:1.0:am:password


Testing

Restart your IdP for your changes before testing.

  • Consult the testing guidance in the Supporting the REFEDS MFA Profile (V5+)#Testing section and use the HelloWorldConfiguration to do localized testing
  • Avoid using aacli for SAML proxying testing as it will not accurately portray an upstream SAML sign-on and attribute assertion
  • If you are a member of a local R&E federation, they may have some MFA Service Providers that can be tested against as well.
  • Alternatively, standing up your own test SP with REFEDS-MFA AuthenticationContext is always an option.