ADFS – SAML 2.0 Identity Provider and SaaS Service Providers

Under ADFS 2.0, Microsoft support the SAML 2.0 IdP Lite and SP Lite modes described in the Liberty Alliance/Kanatara Initiative interop program and eGov Profile 1.5, covering the essentials for identity federation.  From a configuration perspective, we often come across issues in the federation setup phase that can trip up ADFS and the administrator. Sometimes this is the case when dealing with SaaS applications, where there may be minimal customization possible from the vendor standpoint, with the application living in a multi-tenant environment. Tweaking “their side” may not be possible or something that the vendor is comfortable or capable of doing.

In this post, we’ll look at some of the integration issues one may experience when integrating ADFS as an IdP with SAML 2.0 SP web applications using the SAML 2.0 POST profile. There are a number of useful debugging aids/tools that can assist in the troubleshooting process.

All of the above are useful to lean on and gather problem-solving information during troubleshooting.

Scenarios covered here are using ADFS 2.0 as an Identity Provider (IdP).

ADFS Configuration

There are some common gotchas when configuring the relying party in ADFS in the UI.

Issuer / Identifier

Based on the SAML specs, the <samlp:AuthnRequest> must include a <saml:Issuer> including the EntityID of the Service Provider. If you’re creating the relying party manually, double check that the relying party identifier you specify matches that of the EntityID specified in the XML file provided by the Service Provider.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<EntityDescriptor entityID=https://sp.yourdomain.com/yourapp xmlns="urn:oasis:names:tc:SAML:2.0:metadata">

If there’s a mismatch then this may trigger an error:

Exception details:
Microsoft.IdentityServer.Service.Policy.PolicyServer.Engine.ScopeNotFoundPolicyRequestException: MSIS3020: The relying party trust with identifier ‘
https://sp.mydomain.com/foo’ could not be located.

In the above example, the expected identifier is https://sp.yourdomain.com/yourapp was mis-configured in the UI by yours truly with an identifier of https://sp.mydomain.com/foo .. back into the UI to correct…

image

Certificates

Certificates deserve an entirely separate post, but not today Smile…… a few caveats though.. for token signing, the requirement for signing of SAML sign-in requests by the RP is optional. Should you wish to enforce this it is configurable via Powershell … besides importing the requisite token signing certificates and handling the lifecycle management of your certificates in a diligent fashion, bear in mind that if you also want to employ encryption (and possess an encryption certificate) ADFS will sign assertions by default. Moreover, it will use AES-256 for encryption. This can cause issues with service providers that support weaker algorithms. If you experience this issue, then you may be forced to disable claims encryption for that relying party. This can be accomplished through Powershell (set-ADFSRelyingPartyTrust). I’ve not found a way to tweak this in a more refined fashion than on/off…

Another problem to be wary of is certificate revocation checking… if a Certifcate  Distribution Point (CDP) is referenced in the signing or encryption certificate of one of the federation partners, then the corresponding party must be able to reach that CDP in order to determine the validity of the certificate. This can be problematic, for example, if your security policy blocks connections from servers to the Internet or if the counterpart does not publish it over the Internet. 

Secure Hash Algorithm

A common problem when connecting to relying parties, ADFS defaults to the more secure but less well known hash algorithm of SHA-256. SHA-1 is more widely used with the majority of  cryptographic libraries. If you’re getting an error such as this:

SAML request is not signed with expected signature algorithm. SAML request is signed with signature algorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 . Expected signature algorithm is http://www.w3.org/2000/09/xmldsig#rsa-sha1

Flip the secure hash algorithm over to SHA-1 on the Advanced tab in the Relying Party trust to fix this.

SAML Integration

Contained within are some SAML integration issues experienced that I wanted to share.

NameIDPolicy Format URIs

Normally contained with an authentication request is the NameID Policy and format attribute(s). There are numerous ways in which intended or unintended settings can trip up the federation process. When troubleshooting, start with the ADFS event logs as these can be quite revealing.

Here are a couple of sample problem cases.

EXAMPLE 1

PROBLEM: A federation trust has been setup between Organization A and SaaS Provider B. Users attempt to access their SaaS application via SP-initiated logon. After logging on an at ADFS, the SAML redirect returns them to the web application where it fails with a generic HTTP 500 error at the web application.

The SAML authentication request had a NameID Policy that could not be satisfied.
Requestor:
https://sp.yourdomain.com:443/yourapp
Name identifier format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient
SPNameQualifier: 
Exception details: 
MSIS1000: The SAML request contained a NameIDPolicy that was not satisfied by the issued token. Requested NameIDPolicy: AllowCreate: True Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient SPNameQualifier: . Actual NameID properties: Format: urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified, NameQualifier:  SPNameQualifier: , SPProvidedId: .

Looking at the ADFS logs we find MSIS1000 errors. As the above message indicates, the requester is expecting a format of Transient, but we’re sending a format of Unspecified.  Oops…

SOLUTION: The Event Log message also reported that “Use the AD FS 2.0 Management snap-in to configure the configuration that emits the required name identifier.” In other words, fix the identifier… our mismatched name identifier in this case can be fixed by creating a transform Rule and setting the NameID format to what the relying party expects -  transient. In the example below, we take the incoming claim of Common Name and pass it to the Name Identifier in Transient form.

c:[Type == "http://schemas.xmlsoap.org/claims/CommonName"]
=> 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:oasis:names:tc:SAML:2.0:nameid-format:transient");

Note that the use of transient and persistent formats in identifiers are intended to provide privacy-preserving pseudonyms to protect the identity of the subject. Persistent assumes that the identifier assigned to the identity remains the same each time the user logs on, whereas Transient properties are per session. If you want to create opaque identifiers during logon, I suggest reading the following useful article:

http://blogs.msdn.com/b/card/archive/2010/02/17/name-identifiers-in-saml-assertions.aspx

In my case, the application I was using involved personalization and rendered this approach unsuitable. Also, by transforming Common Name into Transient on the claims pipeline, we are also circumventing the privacy-preserving methods that Persistent/Transient were intended for, as the name is no longer obfuscated. Token encryption can be used to overcome this, but that brings its own caveats.

EXAMPLE 2

PROBLEM: A federation trust has been setup between Organization A and SaaS Provider B. Users are attempting to access the SaaS application via an SP-initiated logon process. After logging on an at ADFS, the SAML redirect sends them to the web application where it fails with a generic HTTP 500 error at the web application.

The SAML authentication request had a NameID Policy that could not be satisfied.
Requestor:
https://sp.yourdomain.com/yourapp
Name identifier format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient
SPNameQualifier:
https://sp.yourdomain.com/yourapp
Exception details:
MSIS1000: The SAML request contained a NameIDPolicy that was not satisfied by the issued token. Requested NameIDPolicy: AllowCreate: True Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient SPNameQualifier:
https://sp.yourdomain.com/yourapp. Actual NameID properties: Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient, NameQualifier:  SPNameQualifier: , SPProvidedId: .

Looking at the ADFS logs we see MSIS1000 errors. In this scenario, ADFS is not providing an expected NameQualifier and SPNameQualifier to the Service Provider in the SAML Response.

SOLUTION:

Via a claims rule we can insert the relevant information that the service provider is seeking to satisfy the authentication request.

c:[Type == "http://schemas.xmlsoap.org/claims/CommonName"] => 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:oasis:names:tc:SAML:2.0:nameid-format:transient", Properties["
http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/namequalifier"] = "http://sts.mydomain.com/adfs/services/trust",
Properties["
http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/spnamequalifier"] = "https://sp.mydomain.com/webapp");

Authentication Context

Authentication contexts allow the service provider (SP) to augment in the assertion, the type  and strength of authentication desired. To increase the chance of (successful) interoperability the <samlp:AuthnRequest> should not include a <samlp:RequestedAuthnContext> element. This recommendation bears out in testing with ADFS. The support for different authentication context classes vary and the semantics often interpreted differently. When processing a SAML authentication request, the interpretation of the relative strengths of the different authentication context classes is up to the responder and AD FS 2.0, by default, interprets the relative strength of these different authentication context classes in its own order. By default, this is:

urn:oasis:names:tc:SAML:2.0:ac:classes:Password
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient
urn:oasis:names:tc:SAML:2.0:ac:classes:X509
urn:federation:authentication:windows
urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos

When the service provider is requesting this information, you may find that out-of-the-box it doesn’t work… let’s have a look at some examples …

EXAMPLE 1

PROBLEM : A federation trust has been setup between Organization A and SaaS Provider B. Users are able to logon to the SaaS application via the ADFS Proxy from home or remotely using form-based logon. When they logon internally,  via the ADFS farm, the same users are not able to do so and get an HTTP error. 

<samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="minimum"><saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext>
</samlp:AuthnRequest>

In the above SAML authentication request, the Service Provider (SP) is specifying that the expected authentication context  from ADFS is  urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport. Users doing an integrated windows logon are not emitting this authentication context and instead they transmit urn:federation:authentication:windows in the authentication context to the RP and it may trigger an exception.

SOLUTION: 

There are a couple of ways to tackle this problem:

(a) the service provider may be able to submit the additional authentication context for Windows authentication (urn:federation:authentication:windows) to their configuration.  
(b) if the service provider does not support this change, we can provide the appropriate value in the relying party response by checking for the presence of NameID and then parsing urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport in the response. For example:
 
exists([Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"]) => issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod", Value = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

EXAMPLE 2

PROBLEM: A federation trust has been setup between Organization A and SaaS Provider B. Domain-joined users expecting single sign-on through ADFS are being presented with the forms logon handler when accessing the SaaS application internally. They then have to manually enter their credentials per session.

<samlp:NameIDPolicy  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" AllowCreate="true"></samlp:NameIDPolicy>
<samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="exact"><saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></samlp:RequestedAuthnContext> </samlp:AuthnRequest>

The service provider was, by specifying a comparison of "exact", forcing the authentication context for all users to be set to PasswordProtectedTransport, inadvertently telling ADFS to downgrade authentication for domain-joined clients to forms logon, thus defeating/breaking SSO for internal clients.

SOLUTION: Ensure that the SP sets the RequestedAuthnContext comparison to minimum.

 

That’s it for the moment…… I’ll add to this when new scenarios crop up or via  input from other contributors..

3 thoughts on “ADFS – SAML 2.0 Identity Provider and SaaS Service Providers

  1. Thanks for this great post.

    For anyone else struggling with a debug..

    I was trying to map emailAddress from Active Directory then to NameId, I was getting the error:

    (Microsoft.IdentityServer.Protocols.Saml.InvalidNameIdPolicyException: MSIS7070: The SAML request contained a NameIDPolicy that was not satisfied by the issued token. Requested NameIDPolicy: AllowCreate: True Format: urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress SPNameQualifier: . Actual NameID properties: null.

    With the settings

    c:[Type == “http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname”, Issuer == “AD AUTHORITY”]
    => issue(store = “Active Directory”,
    types = (“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress”,
    “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname”,
    “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname”),
    query = “;mail,givenName,sn;{0}”, param = c.Value);

    and

    c:[Type == “http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress”]
    => 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:oasis:names:tc:SAML:1.1:nameid-format:emailAddress”);

    I spent HOURS scratching my head as to why this wasn’t working – and it turns out that with the Active Directory User Account I had created for testing, I had set up the user so they could authenticate with what I thought was their email address (robert.hatley@contoso.com) but in fact that is their “UserPrincipalName” – you need to go into Active Directory Users and Computers and set their email address (under the ‘General’ tab).

    Aaaaargh.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s