Two-Way Trust between Identity Provider and Relying Party – a solution walkthrough

This article is the second part in a two-part series about Identity Federation. For the first part, see this post.

In the solution walkthrough below, the Identity Provider is referred to as “Company IDP,” and the Relying Party is called “Company RP.” Company RP licenses their cloud-based product to Company IDP. As with any software application in use at their firm, Company IDP’s I.T. management is interested in ensuring they control user access themselves, even though they don’t control Company RP’s system overall. IDP’s I.T. department must be able to deactivate a corporate user account (for example, when an employee leaves the company) and know that this deactivation carried through to all other corporate mission-critical systems, even the ones not owned by Company IDP. I.T. management is also interested in helping their users avoid a very common syndrome, Y.A.U.P. (Yet Another Username and Password).

Company RP has anticipated their clients will have these requirements, and therefore this service provider has built a system that will authenticate users with their corporate login credentials. Nevertheless, Company RP did not want to delegate application flow to any Identity Provider – meaning the cloud product will not pop another window (as is the case in the Photobucket/Twitter example in the previous article) to collect a user’s login information. Rather, Company RP wishes to keep the user wholly within their product screenflow.

A common corporate identity management system is Microsoft Active Directory, which stores information about users.  Importantly, Active Directory is where each user’s username and password are secured. Understanding how widespread and popular Active Directory would be among its corporate clients, Company RP easily decided during the application design phase to build in interoperability with this user credential store through the Active Directory Federation Services (ADFS) component.

The solution covered in this walkthrough consists of these aspects:

  • Transport security
    • SSL-encrypted messaging between RP and IDP
    • Message encryption / decryption certificate*
  • Access Security (allows Company IDP to trust Company RP’s authentication requests, and also permits Company RP to trust Company IDP’s authentication response  — i.e. two-way trust)
    • Relying Party Trust – an explicitly defined identifier in ADFS. This allows Company IDP to trust the request is from Company RP.
    • If an ADFS proxy will not be used, then it is recommended to define a firewall policy restricting ADFS endpoint access to Company RP*
    • Token signature verification*
  • Authentication
    • RP-collected Username / password checked through IDP’s ADFS instance.

* These items are not covered in the solution below and are not mandatory for identity federation but mentioned here for the reader to research further and adopt where appropriate.

At a high-level, the solution can be diagrammed as follows:

adfs auth diagram

Here are some key details behind the diagram above:

  1. User enters his username (email address) and password in the Company RP’s login page. These credentials are the same as what he uses to log on to his corporate network at Company IDP. Company IDP trusts Company RP to collect these credentials and handle them securely.
  2. Company RP’s system passes the login credentials to a predetermined endpoint at Company IDP’s Active Directory Federation Services layer. This message is sent securely over SSL transport. Company IDP will use its Relying Party Trust identifier (explained below) to ensure the system calling it is indeed Company RP’s system.
  3. The username and password are authenticated in Company IDP’s AD instance.
  4. A security token is returned to Company RP’s system.

To make all this come together, a few things have to be done. Let’s look first at how the Relying Party (Company RP) would implement its code to facilitate authentication with the Identity Provider. Then we will take a look at what the Identity Provider (Company IDP) must do to allow this authentication.

What must the Relying Party do?

The following code sample is one possible viable solution that has been tested in the Decisioncell lab. It was developed using ASP.NET WebForms  (Visual Studio 2013, .NET Framework 4.5) and relies on the following libraries:

  • System.IdentityModel
  • System.ServiceModel

Here is the code to accomplish the authentication. Keep in mind the objective is to understand the token request/response process between the Relying Party and the Identity Provider, so this code has been streamlined to highlight that process. The code isn’t presented as production-ready; there are a few gaps — either explicitly stated or implied.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Net;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using System.ServiceModel.Security;

namespace AuthTester
{
    public partial class Login : System.Web.UI.Page
    {

        protected void CompanyRPLogin_Authenticate(object sender, AuthenticateEventArgs e)
        {

            GenericXmlSecurityToken token = null;
            if (TryGetToken(CompanyRPLogin.UserName, CompanyRPLogin.Password, out token) && IsTokenValid(token))
            {
                //TODO: set authentication cookie and redirect user to main application page
            }

        }

        private bool TryGetToken(string user, string pass, out GenericXmlSecurityToken token)
        {
            //WARNING: do not include next line in production code. This line is simply to get around the error related to
            //a self-signed cert: "Could not establish trust relationship
            //for the SSL/TLS secure channel with authority..." '
            ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) =>
            {
                return true;
            };

            bool bResult = false;
            token = null;

            try
            {
                string sADFSEndpoint = "https://adfs.companyidp.com/adfs/services/trust/13/usernamemixed";

                WS2007HttpBinding binding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);

                binding.Security.Message.EstablishSecurityContext = false;
                binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
                binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

                var ep = new EndpointAddress(sADFSEndpoint);

                WSTrustChannelFactory trustChannelFactory = new WSTrustChannelFactory(binding, ep);
                trustChannelFactory.TrustVersion = TrustVersion.WSTrust13;
                trustChannelFactory.Credentials.UserName.UserName = user;
                trustChannelFactory.Credentials.UserName.Password = pass;

                WSTrustChannel tokenChannel = (WSTrustChannel)trustChannelFactory.CreateChannel();

                //create a token request object
                RequestSecurityToken rst = new RequestSecurityToken()
                {
                    RequestType = RequestTypes.Issue,
                    AppliesTo = new EndpointReference("http://FFF260FB-96B5-424F-8208-E1D0C88A3190/COMPANY RP"),
                    KeyType = KeyTypes.Bearer,
                };

                //call ADFS
                RequestSecurityTokenResponse rstr;
                token = tokenChannel.Issue(rst, out rstr) as GenericXmlSecurityToken;
                bResult = true;
            }
            catch (Exception ex)
            {
                bResult = false;
            }

            return bResult;
        }

        private bool IsTokenValid(GenericXmlSecurityToken token)
        {
            bool bResult = false;
            try
            {
               //Optional 1: using Company IDP's public cert key, verify signature.
               //Optional 2: using Company RP's private cert key, decrypt SAML assertion

                //TODO: check attributes in SAML assertion -- such as Active Directory groups for the user, etc.

                bResult = true;
            }
            catch (Exception ex)
            {
                bResult = false;
                //TODO: log error
            }

            return bResult;
        }
    }
}

To help with understanding the code above, the method CompanyRPLogin_Authenticate is an event handler that fires after the user enters his email address and password in the login page and clicks the Login button. The code-flow from there should be easy to follow. If the user successfully authenticates against the Identity Provider’s ADFS instance (handled in TryGetToken method), then a token will be returned. The code also has a stub method (IsTokenValid) meant to imply the token could be interrogated for attributes about the user’s identity, as well as verified with a security certificate.

There are two points about the class above worth special mention:

  • Line 44: the url to the Identity Provider’s ADFS instance would likely be stored in a configuration database that cross-referenced the corporate client with a publicly reachable url for their ADFS proxy. The url is hard-coded here in this manner simply for the sake of clarity.
  • Line 65: the rather odd-looking url here isn’t a url at all, but instead something in ADFS that is called a Relying Party Identifier. We’ll see further down what this means and its role in securing trust between the two systems. The value would also be best stored in a configuration database and is hard-coded here only for the sake of clarity.

The token returned by Company IDP’s ADFS instance (in Line 71 above) is a SAML assertion, and the following is an example of that assertion:

<saml:Assertion MajorVersion="1" MinorVersion="1" AssertionID="_6504871f-a676-4336-bb4c-4635f35a3f10" 
                Issuer="http://adfs.companyidp.com/adfs/services/trust" IssueInstant="2014-05-02T22:34:00.659Z" 
                xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
  <saml:Conditions NotBefore="2014-05-02T22:34:00.627Z" NotOnOrAfter="2014-05-02T23:34:00.627Z">
    <saml:AudienceRestrictionCondition>
      <saml:Audience>http://FFF260FB-96B5-424F-8208-E1D0C88A3190/COMPANY RP</saml:Audience>
    </saml:AudienceRestrictionCondition>
  </saml:Conditions>
  <saml:AttributeStatement>
    <saml:Subject>
      <saml:SubjectConfirmation>
        <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Attribute AttributeName="name" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
      <saml:AttributeValue>John Doe</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute AttributeName="upn" AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
      <saml:AttributeValue>jdoe@companyidp.com</saml:AttributeValue>
    </saml:Attribute>
    <saml:Attribute AttributeName="Group" AttributeNamespace="http://schemas.xmlsoap.org/claims">
      <saml:AttributeValue>Domain Users</saml:AttributeValue>
      <saml:AttributeValue>Help Desk</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
  <saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password" AuthenticationInstant="2014-05-02T22:34:00.518Z">
    <saml:Subject>
      <saml:SubjectConfirmation>
        <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
      </saml:SubjectConfirmation>
    </saml:Subject>
  </saml:AuthenticationStatement>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <ds:SignedInfo>
      <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
      <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
      <ds:Reference URI="#_6504871f-a676-4336-bb4c-4635f35a3f10">
        <ds:Transforms>
          <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
        </ds:Transforms>
        <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
        <ds:DigestValue>MHgQKldxr/Drvi+gIDoM4gaWDgpuzcz/+DRJM8tGnvA=</ds:DigestValue>
      </ds:Reference>
    </ds:SignedInfo>
    <ds:SignatureValue>gSEuT92qMtu5GZfpn+TAhKrv8PvTxIrwcrrPAu+X6Nhz9qh2aOBBoXV8u6MdFdA6hPkpSMuJxUwlcdyaKJD8KNMJeA1ZuMz/jj6qbEEY5hf9FfEzc2c6fF5eTrkcTzpFwKFi1SNnh3JIN0jXyGDx5td0oZnHs8LGvqjhelQJfvDMzdhh8hcdb+QeyIPkosWgES61vnxogoI8eso9X2Gol6AnenYHWqeq4t0k3+G4IcIcI/sms5vl73M+9oEFOnYLZQW3MpXYvzf1EJN/yO683kqD712XaxtA3pSPTn/P5cQ/efujvftfdu5m8ZgqNlf8mV0C+Zz2XXyO2D3EVRuTwg==</ds:SignatureValue>
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <X509Data>
        <X509Certificate>MIIC5DCCAcygAwIBAgIQSduYHLrRO5BKsLABUnANEjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNBREZTIFNpZ25pbmcgLSBQRVRTRVJWRVJWMS5wZXRpLmNvbTAeFw0xNDA0MjcyMzM4MDRaFw0xNTA0MjcyMzM4MDRaMC4xLDAqBgNVBAMTI0FERlMgU2lnbmluZyAtIFBFVFNFUlZFUlYxLnBldGkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAonPi0l3c/GQChBbq90CUh4k9Y+5zJIOtNvPDo5Vzhsd6/g+97jzJ+FnnyXKq0XWmHN9JoNpvwcZntDaMUYP+P6U+eRvHbAgsOLIVkpmPZguAxAPq+nsq5tCQ84IJrZYRyfG66pWAJB/41/Rmg+0xh23BEYrBuWo+n3/8Pfbxn7iDxnDSDdY8zPDeqUlsBYQv0Pk+/j/QnG6b8YElVfFk1F10ympcbP/rTAos/VBxMDwLcKlrA825ImPQYJswjKCWrNjasU5BMwiVq6B5wUwqXa7N7A5W7huuXmcos0ls3A7S0ga3QLpC/5Zklcdg9nnM2Ldsff0L1oFc/luEOx5/AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBYeCBtPifvFWrxTO/eoraVklWq8YOgnojRliotnPexsz9sRsDiDMvbZIKuz8GdQjIAa59kHiiK++KZW8cImYCGEjUOjeQuV5j56Pw13aqfGuVgFD0ih6+fV/TfSBi76sEgL71ZcNnl8YoBppoI2ocXRHVZuHPzOyBDKPQNgVYBzoQGzqjQ/0rJDgWdctQRKtXn1Jvf2ZhmEQA2XVDK1qAJbQl45L80dxV2RwaCzIw1WjgR8ptG+WbSLJ/+t7W3b3UAr/0ZPwTR/Yy36FsHWqn5iyM4UUgtFIQRXEnKDdXTL72SOiPIOqGN9I62t3E78PFfxgMc3V/tHiwvaxHkLCzM</X509Certificate>
      </X509Data>
    </KeyInfo>
  </ds:Signature>
</saml:Assertion>

If a token is returned from the Identity Provider’s ADFS, then that signals to the Relying Party the user’s login credentials are valid. Additionally, the SAML assertion can be inspected for attributes the ADFS system is willing to share (see lines 15 – 24 above). Now let’s take a look at what must be done on the Identity Provider side within ADFS to permit this type of identity federation.

What must the Identity Provider Do to Allow A Relying Party to Authenticate Users Against Its On-Premises Active Directory?

Microsoft Active Directory is widely used across the corporate landscape. It is a robust repository for user credentials. Active Directory Federation Services (ADFS) is a companion system that enables Identity Federation with Active Directory, and it is in ADFS where the remainder of the setup work must be done. The steps below are organized in the following categories:

  • Enable Endpoint
  • Setup Explicit Relying Party Trust
  • Define Claim Rules

Enable endpoint: in Active Directory Federation Services Management Console,ensure the proper endpoint is enabled. The example below uses AD FS 2.0 (Windows Server 2008 R2 Enterprise). The endpoint used by the C# code above is /adfs/services/trust/13/usernamemixed . If Company IDP has an ADFS proxy, then make sure the Proxy Enabled option is set to Yes.

adfs endpoints

Setup Explicit Relying Party Trust: Next to do in the ADFS management console is right-click on the Relying Party Trusts folder (next image) and choose “Add Relying Party Trust”.

adfs rp trust

Choosing the “Add Relying Party Trust” option will activate the RP Trust Wizard. This is the first screen:

ADFS rpt trust wizard

Click the Start button (above) and the next screen appears:

adfs rp trust

Choose the option “Enter data about the relying party manually” (above) and click Next to display the following screen:

adfs rp trust

Enter the information as suggested in the image above. Click Next to see the next screen:

adfs rp trust

Choose the option indicated above (AD FS 2.0 profile). Important: for this setup: a certificate will not be used. Nor will the “Configure Url” option. Skip past those two screens to get to the next screen:

ADFS rp trust

The Relying Party Trust Identifier (above) is an important part of securing the trust between Company IDP and Company RP. Company RP will provide Company IDP with a trust identifier, and this identifier must be in URI format . In the image above, the following URI has been entered:

http://FFF260FB-96B5-424F-8208-E1D0C88A3190/COMPANY RP

Let’s take a moment to understand the parts of the Trust Identifier:

  • http:// : This first part is included merely so the URI format is followed. The URI itself is not reachable because it is not an addressable location on the web.
  • FFF260FB-96B5-424F-8208-E1D0C88A3190 : This is a randomly generated GUID that Company RP produces. It guarantees uniqueness for the trust identifier. If Company RP has multiple clients with whom each has defined a Relying Party Trust Identifier in their ADFS instances, then it is recommended that Company RP do not reuse these GUIDs, but rather generate a new GUID for each client. In the C# code, reviewed above, Company RP would need to ensure the proper GUID is matched to the client. 
  • COMPANY RP : This is merely user-friendly text to indicate to Company IDP I.T. staff for whom this trust identifier was established.

Click Next (above) to see the following screen:

adfs rp trust

Make sure the “Permit all” option is selected (above), then click the Next button to see this screen:

adfs rp trust

The screen above provides an opportunity to review the preceding configuration steps. Click Next to complete the wizard.

Define Claim Rules (optional): Now that the Relying Party Trust has been added, claim rules can be defined in ADFS if that is a requirement for a trust between IDP and RP. “Claims” are the attributes about the user stored in Active Directory at Company IDP. The user information that can be stored in Active Directory consists of very common things like the user’s full name and the domain groups to which a user belongs, but also Active Directory is capable of holding other types of information. Attributes such as employee department, employee ID, address, telephone number, employee title, etc., can also be stored in Active Directory. Which of these attributes are included in the token sent from the Identity Provider’s ADFS to the Relying Party is a matter to be determined between those organizations. Let’s say Company RP only needs the user’s full name, email address, and Active Directory groups, so that is what Company IDP has agreed to return in the token.

In ADFS management console, highlight the Relying Party Trusts folder and select the new trust that was setup in the walkthrough above:

adfs edit claims 1

Right-click on the RP Trust (image above) and select “Edit Claim Rules”. The following dialog is presented:

adfs edit claims 2

Click on the “Add Rule” button (image above) to launch the Add Transform Claim Rule Wizard:

adfs edit claims 3

Keep the default rule template set to “Send LDAP Attributes as Claims” and click the Next button to display the following screen:

adfs edit claims 4

As shown in the SAML assertion further up in this solution walkthough, the Relying Party is receiving from the Identity Provider the user’s name, the email address, and the domain groups for that user. Again, these attributes are not required to be included in all tokens sent back to all relying parties, but perhaps Company RP’s system requires this information to ensure the user is properly handled within their system context. For Company IDP to send the user attributes included in the SAML sample above, then the LDAP attributes to Claim Types cross-map should be setup as follows:

adfs edit claims 5

Click the Finish button. This completes the setup work in ADFS. Company IDP should be all set to start accepting authentication requests from and issuing tokens to Company RP, but if the Identity Provider is not using an ADFS proxy, that company will need to review any firewall policies around access to the ADFS endpoint enabled above.