If you’ve ever attempted to integrate a Shibboleth Service Provider (Relying Party) application with ADFS, you’d have quickly realised that Shibboleth and ADFS are quite different beasts. This blog covers off some of the key issues involved and provides details on how to get ADFS to play nice with a Shibby Service Provider (SP). This blog does not cover configuring ADFS to participate as a member in a Shibboleth Federation like InCommon or the Australian Access Federation (AAF). That type of integration presents a different set of challenges, contact us to discuss your needs.
Before we get to the details, some terminology varies between the two federation services, the table below lists the key differences. Shibboleth terminology will be used throughout this blog.
AD FS Name | Shibboleth Name |
Security token | Assertion |
Claims provider | Identity provider (IdP) |
Relying party | Service provider (SP) |
Claims | Assertion attributes |
Below are the key issues involved with integration of Shibboleth SPs with ADFS. Each will be expanded upon later in this blog.
1. Metadata incompatibility.
2. Incorrect SAML Name Format in assertions.
3. Missing Assertion Attributes.
1. Metadata Incompatibility
ADFS generates publishes its metadata https://<FederationServiceName>/FederationMetadata/2007-06/FederationMetadata.xml. There is no functionality to modify what is published. When a Shibboleth SP consumes ADFS metadata the following issues can arise:
1. ADFS Metadata contains information pertaining to WS.Trust and WS.Federation. This is not consumed by Shibboleth SPs.
2. Shibboleth Scope information is not generated by ADFS.
3. Shibboleth XML Name Space information is missing.
The easiest method to address these issues is to pre-process the metadata for consumption by the Shibboleth SP. For this, the Federation Metadata Manager (FEMMA) toolset is useful. This toolset was developed to assist in configuring ADFS to participate as a member in a Shibboleth Federation (e.g InCommon or Australian Access Federation). Whilst this is overkill for integration of a Shibby SP with ADFS, also included within this toolset is the ADFS2Fed.py Python script with reads ADFS metadata and corrects the above issues. Run it, configure the Shibboleth SP to retrieve IdP metadata from a local file, job done!
2. Incorrect SAML Name Format
When ADFS issues assertions configured using the standard ADFS Claims Rules interface it uses the name format urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified. Shibboleth expects urn:oasis:names:tc:SAML:2.0:attrname-format:uri. This issue unfortunately means that assertions will need to be issued by custom Claim Rules.
To apply the correct SAML Name Format to an assertion attribute from an ADFS attribute store, a two stage process is needed:
1. Retrieve the assertion attribute from the attribute store and store as an incoming assertion. For example, the custom ADFS Claims Rule below queries Active Directory for the authenticating user’s User Principal Name and stores the value into the incoming assertion with type of http://kloud/internal/userprincipalname:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"] => add(store = "Active Directory", types = ("http://kloud/internal/userprincipalname"), query = ";userPrincipalName;{0}", param = c.Value);
2. Issue a new assertion of the required type using the incoming assertion value. The following custom ADFS Claims Rule retrieves the incoming assertion of type http://Kloud/internal/userprincipalname, re-issues it as type urn:oid:1.3.6.1.4.1.5923.1.1.1.6, and assigns a name format of urn:oasis:names:tc:SAML:2.0:attrname-format:uri.
c:[Type == "http://kloud/internal/userprincipalname"] => issue(Type = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6", Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/attributename"] = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
3. Missing Assertion Attributes
By default, a Shibboleth SP expects assertions from the eduPerson class. Some of these have specific requirements, below are the troublesome ones and sample ADFS custom Claim Rules to get you going. Note – Scoped attributes must have a scope matching the scope provided in the IdP metadata, or by default the Shibboleth SP will drop them. If using the FEMMA ADFS2Fed.py script, the Shibboleth Scope is entered as a parameter.
1. eduPersonTargetedID (urn:oid:1.3.6.1.4.1.5923.1.1.1.10)
This assertion attribute is derived from SAML NameID. It is required to identify the Identity Provider, Service Provider and provide a consistent and obfuscated identifier for the user. Obfuscation of the user identifier ensures that whilst the user can be tracked across services, they cannot be identified directly to a named account.
To construct this first we grab an immutable identifier for the user – the users Active Directory Security Identifier (SID) is ideal as it is constant for the life of the account unlike Windows Account Name (sAMAccountName) which can change. We then use the ppid function to encrypt the SID using the federation service name of ADFS as a seed. Finally we store this value as an incoming assertion type of http://kloud/internal/persistentid.
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"] => add(store = "_OpaqueIdStore", types = ("http://kloud/internal/persistentId"), query = "{0};{1};{2}", param = "ppid", param = c.Value, param = c.OriginalIssuer);
Next we the provide the IdP and SP name qualifiers (EntityIDs) as static strings and issue the assertion as the required type – urn:oid:1.3.6.1.4.1.5923.1.1.1.10:
c:[Type == "http://kloud/internal/persistentId"] => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oid:1.3.6.1.4.1.5923.1.1.1.10", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/spnamequalifier"] = "<SP Entity ID>", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/namequalifier"] = "<IdP Entity ID>");
2. eduPersonUniqueId (urn:oid:1.3.6.1.4.1.5923.1.1.1.13)
Similarly to eduPersonTargetID, this Assertion Attribute is another obfuscated user identifier. It is scoped, therefore it is comprised of a unchanging unique identifier of the user concatenated with their domain e.g. <identifier>@kloud.com.au. Examples given in the eduPerson schema reference show a GUID as the user identifier. The Active Directory GUID fits requirements, however performing a query against Active Directory for the GUID value as shown will not result in a correctly formatted GUID. This is due to the conversion of the GUID binary value from Active Directory.
Recommended is either to implement a String Processing Store, or populate an attribute store with a GUID converted to a correctly formatted string. Once the GUID value is converted and stored in the incoming assertions pipeline is can be concatenated with the Scope value and assigned the correct name format, as shown below:
c:[Type == "http://kloud/internal/objectguid"] => issue(Type = "urn:oid:1.3.6.1.4.1.5923.1.1.1.13", Value = c.Value + "@kloud.com.au", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/attributename"] = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
3. eduPersonPrincipalName (urn:oid:1.3.6.1.4.1.5923.1.1.1.6)
This assertion attribute provides a non-obfuscated, scoped user identifier. Active Directory provides a User Principal Name value which is suitable for this purpose. It can be queried and stored into the incoming assertion pipeline like so:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"] => add(store = "Active Directory", types = ("http://kloud/internal/userprincipalname"), query = ";userPrincipalName;{0}", param = c.Value);
And issued in with the correct type and name format like this:
c:[Type == "http://kloud/internal/userprincipalname"] => issue(Type = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6", Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/attributename"] = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
4. eduPersonAffiliation (urn:oid:1.3.6.1.4.1.5923.1.1.1.1)
This attribute is used to categorise user accounts for the purpose of assigning access privileges. Refer to the eduPerson schema reference for a definition of affiliations. Methods to assign affiliation values vary between organisations, however in regards to issuing an assertion the recommended approach would be to populate an attribute either in Active Directory or another attribute store. Below is an example where the Active Directory attribute Employee Type (employeeType) stores the affiliation:
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"] => add(store = "Active Directory", types = ("http://kloud/internal/affiliation"), query = ";employeeType;{0}", param = c.Value);
And issued in with the correct type and name format like this:
c:[Type == "http://kloud/internal/affiliation"] => issue(Type = "urn:oid:1.3.6.1.4.1.5923.1.1.1.1", Value = c.Value, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/attributename"] = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
To issue eduPersonScopedAffiliation (urn:oid:1.3.6.1.4.1.5923.1.1.1.9) issue the incoming assertion concatenated with the scope as below:
c:[Type == "http://kloud/internal/affiliation"] => issue(Type = "urn:oid:1.3.6.1.4.1.5923.1.1.1.9", Value = c.Value + "@kloud.com.au", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/attributename"] = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
5. eduPersonAssurance (urn:oid:1.3.6.1.4.1.5923.1.1.1.11)
The assertion provides information on the levels of assurance involved in the authentication of a user, both in the proof required for account creation and the strength of authentication mechanism/s used. The eduPersonAssurance assertion is a text string, e,g. “urn:mace:aaf.edu.au:iap:id:1” indicates an Identity Assurance Level of 1, whilst “urn:mace:aaf.edu.au:iap:authn:1” shows an Authentication Assurance Level of 1. In most instances this assertion can be issued as a simple value assertion as below:
=> issue(Type = "urn:oid:1.3.6.1.4.1.5923.1.1.1.11", Value = "urn:mace:aaf.edu.au:iap:id:1", Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/attributename"] = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri");
Hopefully this blog helps you with your Shibboleth integration issues. In addition, federation services should be implemented with alignment to a larger identity and access management strategy. If you need assistance call us and we’ll sort it out for you!
Mark,
Nice article. I was hoping for a little more detail on what needs to be changed/edited in Shibboleth SP to be configured to work with ADFS IdP. This article seems to mostly address the adfs changes.
James
Hi James, the blog details the changes to an ADFS deployment to emulate a Shibboleth IdP so that configuration of the SP would be as it would with a Shibboleth IdP. There are many resources on that topic – a good one below:
https://wiki.shibboleth.net/confluence/display/SHIB2/Configuration
If you are referring to the requirements to integrate a Shibboleth SP with an ADFS deployment not configured to emulate a Shibboleth IdP, then yes the blog does not cover that. The requirements for that would vary greatly between SPs.
Hi. I am trying to concatenate two values that are being pulled dynamically from the database but receive syntax errors.
Here is how I am trying to concatenate the values.
c1:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname”,
value== “(store = “X Database”, types = (“s”), query = “SELECT [A] FROM [B] WHERE [UsernameTxt]={0}”, param = c1.Value] &&
C2:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname”,
value== “(store = “Y Database”, types = (“t”), query = “SELECT [C] FROM [D] WHERE [UsernameTxt]={0}”, param = c2.Value]
==>issue (Type= “http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname”,Value=c1.Value +c2.Value);
Can someone please let me know how to get it fixed.
Thank you
Hi Vijay,
Something like the claims set below might work for you. The first two claims retrieve the values from the database using the Windows Account Name value and add these to the incoming claims. The third claim constructs the combined value and adds a new value to the outgoing claim set. If you’re cutting and pasting, sometimes you’ll need to re-type quotes as these can get a bit messed up. Also make sure you have consistent brackets (open and close) and capitalisation.
Claim One:
c:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname”]
=> add(store = “X Database”, types = (“http://kloud/internal/value1”), query = query = “SELECT [A] FROM [B] WHERE [UsernameTxt]={0}”, param = c.Value);
Claim two:
c:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname”]
=> add(store = “X Database”, types = (“http://kloud/internal/value2”), query = query = “SELECT [C] FROM [BD WHERE [UsernameTxt]={0}”, param = c.Value);
Claim three:
c1:[Type == “http://kloud/internal/value1”] &&
c2:[Type == “http://kloud/internal/value2″]
=> issue(Type=”http://kloud/internal/combinedvalues”, Value=c1.value+c2.value);