This wiki page page was contributed by John Morrison at Uppsala University.

Here are the working Instructions that to need to be configured with Shibboleth version 3.2.1 to enable the function of Office365 web portal and Office clients such as Outlook and Office mobile apps. The focus on the page is how you configure Shibboleth, not on how to configure Azure AD for directory syncronization and federated use.

Office 365

# Configuring the SSO
# The following scripts needs to be executed to configure the Single Sign-On. I have used the idp-signing.crt certificate in
# the script because this certificate is self signed and this certificate is available in the IDP_HOME/credentials directory.

$dom = "<Replace Your Federated Domain>"
$idpHost="<Replace Your IDP Host>"
$fedBrandName="<Replace with your organisation abbreviation> Shibboleth"
$url = "$idpHost/idp/profile/SAML2/POST/SSO"
$ecpUrl = "$idpHost/idp/profile/SAML2/SOAP/ECP"
$uri = "$idpHost/idp/shibboleth"
$logoutUrl = "$idpHost/idp/profile/SAML2/POST/SLO" 

$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("idp-signing.crt")
$certData = [system.convert]::tobase64string($cert.rawdata)

Set-MsolDomainAuthentication -DomainName $dom -federationBrandName $FedBrandName -Authentication Federated  -PassiveLogOnUri $url -SigningCertificate $certData -IssuerUri $uri -ActiveLogOnUri $ecpUrl -LogOffUri $logoutUrl -PreferredAuthenticationProtocol SAMLP

Get-MsolDomainFederationSettings -DomainName $dom


Shibboleth

 

Please note: For ECP to work the username part of Active Directory userPrincipalName must be the same as the username users use to login to the WebSSO part of Shibboleth.

Technical configuration

  1. Add relying party specific configuration.  Azure requires that encryption be turned off and that only assertions be signed.  If you're going to support ECP its will also necessary to set a name identifier precedence so that the ECP endpoint responds with a format of "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent".  The following bean definition can be used verbatim in your configuration.

         <util:list id="shibboleth.RelyingPartyOverrides">
     
            <bean parent="RelyingPartyByName"
                  c:relyingPartyIds="#{{'urn:federation:MicrosoftOnline'}}">
                <property name="profileConfigurations">
                    <list>
                    <bean parent="SAML2.SSO" 
                            p:encryptAssertions="false"
                            p:signAssertions="true" 
                            p:signResponses="false" />
                    <bean parent="SAML2.ECP" 
                            p:encryptAssertions="false" 
                            p:signAssertions="true" 
                            p:signResponses="false"
                            p:nameIDFormatPrecedence="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" />
                    <ref bean="SAML2.Logout" />
                    </list>
                </property>
            </bean>
    
        </util:list>


  2. Configure the necessary attribute definitions, data connector and filter policy.  Only one SAML attribute, entitled "IDPEmail," should be sent. This should be your local Active Directory UPN or userPrincipalName. Another attribute definition is typically required in order to send the Azure ImmutableID in the SAML Subject. The ImmutableID attribute is site dependent, but most frequently maps to the "objectGuid" in Active Directory. Here we use the reference data connector msLDAP to obtain the objectGuid from AD.
    (Note: If you use Active Directory as your primary attribute resource remove activationConditionRef="Office365Condtion" in three places below.)

     <!-- ImmutableID for Office365. Used for the NameID value. (No encoder necessary) -->
    <resolver:AttributeDefinition id="ImmutableID" xsi:type="Simple" xmlns="urn:mace:shibboleth:2.0:resolver:ad" sourceAttributeID="objectGUID" activationConditionRef="Office365Condtion">
    		<resolver:Dependency ref="msLDAP" />
    </resolver:AttributeDefinition>
     
    <!-- UserPrincipalName for Office365 and local Active Directory. -->
    <resolver:AttributeDefinition id="UserId" xsi:type="ad:Simple" sourceAttributeID="userPrincipalName" activationConditionRef="Office365Condtion">
    		<resolver:Dependency ref="msLDAP" />
    		<resolver:AttributeEncoder xsi:type="enc:SAML2String" name="IDPEmail" friendlyName="UserId" />
    </resolver:AttributeDefinition>

    (Note: the objectGUID attribute should be declared as a binary attribute if it is not already within the msLDAP confiugration, see the example on Microsoft's Technet article)

     

    <resolver:DataConnector id="msLDAP" xsi:type="dc:LDAPDirectory" xmlns="urn:mace:shibboleth:2.0:resolver:dc"
        ldapURL="ldaps://dc.domain.se:636"
        baseDN="OU=X,DC=DOMAIN,DC=DOMAIN,DC=SE"
        principal="CN=ACCOUNT,OU=X,DC=DOMAIN,DC=SE"
        principalCredential="PASSWORD"
        searchScope="SUBTREE"
    	activationConditionRef="Office365Condtion">
      <dc:FilterTemplate>
        <![CDATA[
                    (sAMAccountName=${requestContext.principalName})
                ]]>
      </dc:FilterTemplate>
      <ReturnAttributes>objectGUID</ReturnAttributes>
    <dc:LDAPProperty name="java.naming.ldap.attributes.binary" value="objectGUID"/>
    </resolver:DataConnector>

    (Note: If you use Active Directory as your primary attribute resource this part you don't need to add the data connector but check that <dc:LDAPProperty name="java.naming.ldap.attributes.binary" value="objectGUID"/> is in the data connector)

     

    <!-- Attribute Filter Policy for Azure AD -->
    <AttributeFilterPolicy id="PolicyForWindowsAzureAD">
            <PolicyRequirementRule xsi:type="Requester" value="urn:federation:MicrosoftOnline" />
                  
    <!-- Release userPrincipalName as Azure AD User ID -->
            <AttributeRule attributeID="UserId">
                    <PermitValueRule xsi:type="ANY"/>
            </AttributeRule>
    <!-- Release Immutable ID to Azure AD -->
            <AttributeRule attributeID="ImmutableID">
                    <PermitValueRule xsi:type="ANY"/>
            </AttributeRule>
    </AttributeFilterPolicy>
    
    


  3. Since Azure requires use of a proprietary identifier in conjunction with the standard NameID format of "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent," you will need to create activation conditions to send that value to Azure only. The following configuration example is for reference purposes and must be modified as appropriate to your environment.

        <!-- SAML 2 NameID Generation -->
        <util:list id="shibboleth.SAML2NameIDGenerators">
        
    	    <ref bean="shibboleth.SAML2TransientGenerator" />
        
    	    <ref bean="shibboleth.SAML2PersistentGenerator" />
                         
        <!-- Persistent ID Generator for all entities except Microsoft -->
        
    	<bean parent="shibboleth.SAML2PersistentGenerator">
                     <property name="activationCondition">
                <bean parent="shibboleth.Conditions.NOT">
                    <constructor-arg>
                        <bean parent="shibboleth.Conditions.RelyingPartyId" c:candidates="#{{'urn:federation:MicrosoftOnline'}}" />
                    </constructor-arg>
                </bean>
            </property>
        </bean>
    
        <!-- Microsoft requires a custom Persistent ID Generator that sends the AD GUID -->
    
        <bean parent="shibboleth.SAML2AttributeSourcedGenerator"
                           p:format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
                  p:attributeSourceIds="#{ {'ImmutableID'} }">
            <property name="activationCondition">
                <bean parent="shibboleth.Conditions.RelyingPartyId" c:candidates="#{{'urn:federation:MicrosoftOnline'}}" />
            </property>
        </bean>
           
        </util:list>


  4. (Optional) Add a condition in global.xml if you want reduce data connector lookup's for ObjectGIUD for Office365 authentications.
    (Note: If you use Active Directory as your primary attribute resource don't add this condition.)

    <!-- Single SP -->
    <bean id="Office365Condtion" parent="shibboleth.Conditions.RelyingPartyId"
        c:_0="#{{'urn:federation:MicrosoftOnline'}}" />
    


  5. Configure your IdP to load the Azure metadata. Best to download this URL in the metadata directory and load it via FilesystemMetadataProvider.

    <MetadataProvider id="OFFICE365MD" xsi:type="FilesystemMetadataProvider" 
            xmlns="urn:mace:shibboleth:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="urn:mace:shibboleth:2.0:metadata http://shibboleth.net/schema/idp/shibboleth-metadata.xsd"
            failFastInitialization="true"
            metadataFile="/usr/local/shibboleth-idp/metadata/federationmetadata.xml">
    </MetadataProvider>


  6. Only if you will support ECP! ECP is enabled out of the box with version 3, but you may protect it with apache config 

    <Location /idp/profile/SAML2/SOAP/ECP>
    AuthType Basic
    AuthName "Username/Password"
    AuthBasicProvider ldap
    AuthLDAPUrl "ldaps://dc.domain.se:636/OU=O365,DC=DOMAIN,DC=SE?sAMAccountName?sub?(objectClass=*)" NONE
    #AuthLDAPBindDN "uid=shib,ou=ldapclient,dc=DOMAIN,dc=SE"
    AuthLDAPBindPassword "PASSWORD"
    Require valid-user
    </Location>


Testing ECP

Only if you will support ECP! Below you can use an ECP test script to see that it works correctly.

#!/bin/bash
# Simple test if an IdP's ECP endpoint is running
# --------------------------------------------------------------------
#
# You need to set these parameters
#
#
# userid = a valid login id on your system
userid="TESTUID:PASSWORD"

# idpid = your idp entity id
idpid="https://IdPURL/idp/shibboleth"

# ecpurl = your idp's ECP URL
ecpurl="https://IdPURL/idp/profile/SAML2/SOAP/ECP"

# rpid = a valid SP entityId that is configured for ECP
rpid="urn:federation:MicrosoftOnline"


# acsurl,ascurlbinding = an AssertionConsumerService URL and binding
acsurl="https://login.microsoftonline.com/login.srf"


acsurlbinding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"


#
# --------------------------------------------------------------------
#

# create a soap request
now="`date -u +%Y-%m-%dT%H:%M:%S`"
id="_`uuidgen | tr -d '-'`"


# note. possible id if you dont have uuidgen: "_a7849c4e7188d592b1a266a34332ffbe"
envelope='<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><S:Body><samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" AssertionConsumerServiceURL="'${acsurl}'" ID="'${id}'" IssueInstant="'${now}'" ProtocolBinding="'${acsurlbinding}'" Version="2.0"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'${rpid}'</saml:Issuer><samlp:NameIDPolicy AllowCreate="1"/><samlp:Scoping><samlp:IDPList><samlp:IDPEntry ProviderID="'${idpid}'"/></samlp:IDPList></samlp:Scoping></samlp:AuthnRequest></S:Body></S:Envelope>'

# make the request
resp="`curl -k -s -H \"Content-Type: text/xml; charset=utf-8\" -d \"$envelope\" -u \"${userid}\" \"${ecpurl}\" `"


# examine what we got
echo "$resp" > testecp.out
echo "$resp" | grep -q "Fault>"
[[ $? == 0 ]] && {
   echo "ECP request failed: soap fault!"
   echo "$resp"
   exit 1
}
echo "$resp" | grep -q "RequestUnsupported"
[[ $? == 0 ]] && {
   echo "ECP request failed: unsupported!"
   echo "$resp"
   exit 1
}
echo "$resp" | grep -q "status:Success"
[[ $? == 0 ]] && {
   echo "ECP request successful!"
   exit 0
}
echo "ECP request failed.  response:"
echo "$resp"
exit 1

 

This is the output of the script containing the information that should be sent up to Office365. Notice the SAML Subject NameID envelope in the right format with the ObjecGUID (ImmutableID=ZNr1xunarUiBZ5UkKF88sg==). And one attribute UserId sent as the UPN.

        <saml2:Subject>
          <saml2:NameID xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" NameQualifier="https://IdPURL/idp/shibboleth" SPNameQualifier="urn:federation:MicrosoftOnline">ZNr1xunarUiBZ5UkKF88sg==</saml2:NameID>
          <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
            <saml2:SubjectConfirmationData Address="122.123.123.111" InResponseTo="_fda2a3986d8849a4b558b8c86e1bfd45" NotOnOrAfter="2016-12-13T09:54:22.199Z" Recipient="https://login.microsoftonline.com/login.srf"/>
          </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions NotBefore="2016-12-13T09:49:22.166Z" NotOnOrAfter="2016-12-13T09:54:22.166Z">
          <saml2:AudienceRestriction>
            <saml2:Audience>urn:federation:MicrosoftOnline</saml2:Audience>
          </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AuthnStatement AuthnInstant="2016-12-13T09:49:21.138Z" SessionIndex="_ddcae7edd7371f939341f1ce595e5202">
          <saml2:SubjectLocality Address="122.123.123.111"/>
          <saml2:AuthnContext>
            <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
          </saml2:AuthnContext>
        </saml2:AuthnStatement>
        <saml2:AttributeStatement>
          <saml2:Attribute FriendlyName="UserId" Name="IDPEmail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
            <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">uid@test.se</saml2:AttributeValue>
          </saml2:Attribute>
        </saml2:AttributeStatement>

 

Testing SAML assertion

A SAML test with Shibboleth's own tool aacli.sh: (Note: this only happens when Azure metadata is specifically using  <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat> as the preferred NameID Format).

 /usr/local/shibboleth-idp/bin/aacli.sh -u=https://IdPURL/idp -n=USER_UID -r=urn:federation:MicrosoftOnline --saml2

<?xml version="1.0" encoding="UTF-8"?>
<saml2:Assertion ID="_dc3a482f923bab7e43f23a04d2529527"
    IssueInstant="2016-12-13T13:21:53.583Z" Version="2.0" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
    <saml2:Issuer>https://IdPURL/idp/shibboleth</saml2:Issuer>
    <saml2:Subject>
        <saml2:NameID
            Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
            NameQualifier="https://IdPURL/idp/shibboleth" SPNameQualifier="urn:federation:MicrosoftOnline">ZNr8xunarUiBZ0UkKF95sg==</saml2:NameID>
    </saml2:Subject>
    <saml2:AttributeStatement>
        <saml2:Attribute FriendlyName="UserId" Name="IDPEmail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
            <saml2:AttributeValue
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">uid@test.se</saml2:AttributeValue>
        </saml2:Attribute>
    </saml2:AttributeStatement>
</saml2:Assertion>