How to use Shibboleth Identity Provider v3 with Office 365

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

  • Activate Domain Synchronisation with Azure AD Connect between your Office 365 tenant and your local Acitve Directory, don't sync more than necessary. You should not sync you users passwords to Azure AD in this federated setup.
  • Convert your domain to a federated domain using Powershell script below with your own configuration.
  • Now it is best to avoid the use of the old proxy ECP method, and enable modern authentication (OAuth) for your Exchange Online tenant instead. This allows the Outlook software client to function correctly with Shibboleth without the use of ECP, Howto_Link. The Office 365 Application passwords or the federated SAML ECP can be used for those clients that can't use OAuth or use of older legacy protocols like IMAP and SMTP.
Powershell to Activate Federated login in Office365
# 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


  • Add support for IdPEmail and ImmutableID attributes to your IdP
    • a) in the Shibboleth resolver and filter
    • b) add a NOT condition in saml-nameid.xml file to block generation of global persistentID but push a custom persistant NameID for Office365 only.
    •  optional: use a condition to allow only the entityID: "urn:federation:MicrosoftOnline" to connect to Azure AD only, all other attributes connect with LDAP.
    • c) (Optional) If you don't use your local Active Directory as the primary attribute source add a condition in global.xml if you want reduce Active Directory data connector lookup's to Office365 only.

  • Only if you will support ECP! Protect Shibboleth's ECP handler with LDAP Basic Auth (required by Outlook and other mail clients)
  • Add the Office 365 metadata to a local loaded metadata file


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.

    Excerpt: relying-party.xml
         <util:list id="shibboleth.RelyingPartyOverrides">
            <bean parent="RelyingPartyByName"
                <property name="profileConfigurations">
                    <bean parent="SAML2.SSO" 
                            p:signResponses="false" />
                    <bean parent="SAML2.ECP" 
                            p:nameIDFormatPrecedence="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" />
                    <ref bean="SAML2.Logout" />
  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.)

    Excerpt: attribute-resolver.xml
     <!-- 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" />
    <!-- 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" />

    (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)


    Excerpt: attribute-resolver.xml
    <resolver:DataConnector id="msLDAP" xsi:type="dc:LDAPDirectory" xmlns="urn:mace:shibboleth:2.0:resolver:dc"
    <dc:LDAPProperty name="java.naming.ldap.attributes.binary" value="objectGUID"/>

    (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)


    Excerpt: attribute-filter.xml
    <!-- 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"/>
    <!-- Release Immutable ID to Azure AD -->
            <AttributeRule attributeID="ImmutableID">
                    <PermitValueRule xsi:type="ANY"/>
  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.

    Excerpt: saml-nameid.xml
        <!-- 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">
                        <bean parent="shibboleth.Conditions.RelyingPartyId" c:candidates="#{{'urn:federation:MicrosoftOnline'}}" />
        <!-- Microsoft requires a custom Persistent ID Generator that sends the AD GUID -->
        <bean parent="shibboleth.SAML2AttributeSourcedGenerator"
                  p:attributeSourceIds="#{ {'ImmutableID'} }">
            <property name="activationCondition">
                <bean parent="shibboleth.Conditions.RelyingPartyId" c:candidates="#{{'urn:federation:MicrosoftOnline'}}" />
  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.)

    Excerpt: global.xml
    <!-- 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.

    Excerpt: metadata-providers.xml
    <MetadataProvider id="OFFICE365MD" xsi:type="FilesystemMetadataProvider" 
            xmlns="urn:mace:shibboleth:2.0:metadata" xmlns:xsi=""
  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 

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

Testing ECP

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

Bash script testing ECP
# Simple test if an IdP's ECP endpoint is running
# --------------------------------------------------------------------
# You need to set these parameters
# userid = a valid login id on your system

# idpid = your idp entity id

# ecpurl = your idp's ECP URL

# rpid = a valid SP entityId that is configured for ECP

# acsurl,ascurlbinding = an AssertionConsumerService URL and binding


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

# 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="" 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.

SAML trace output
          <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="" InResponseTo="_fda2a3986d8849a4b558b8c86e1bfd45" NotOnOrAfter="2016-12-13T09:54:22.199Z" Recipient=""/>
        <saml2:Conditions NotBefore="2016-12-13T09:49:22.166Z" NotOnOrAfter="2016-12-13T09:54:22.166Z">
        <saml2:AuthnStatement AuthnInstant="2016-12-13T09:49:21.138Z" SessionIndex="_ddcae7edd7371f939341f1ce595e5202">
          <saml2:SubjectLocality Address=""/>
          <saml2:Attribute FriendlyName="UserId" Name="IDPEmail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
            <saml2:AttributeValue xmlns:xsi="" xsi:type="xsd:string"></saml2:AttributeValue>


Testing SAML assertion

A SAML test with Shibboleth's own tool (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).

aacli output
 /usr/local/shibboleth-idp/bin/ -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">
            NameQualifier="https://IdPURL/idp/shibboleth" SPNameQualifier="urn:federation:MicrosoftOnline">ZNr8xunarUiBZ0UkKF95sg==</saml2:NameID>
        <saml2:Attribute FriendlyName="UserId" Name="IDPEmail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
                xmlns:xsi="" xsi:type="xsd:string"></saml2:AttributeValue>