SAML SSO

SSO stands for single-sign on, which means a user authenticates only once, within one system, but the user has access to resources from an entirely separate system. A common scenario is when the user logs in to the portal provided by his internet service provider, then once in the web application for that ISP, he clicks on a link that takes him to another application hosted by another company, yet he is not required to login again to this different application.

SAML 2.0 allows this type of cross-system authentication to occur. SSO can take on several forms and process flows, but the main focus of this article is the following process flow:

  • User authenticates with Identity Provider (IdP) application. In figure 1 below, the user enters her login credentials into a web page in a standard browser window.
  • User requests a resource that exists with a Service Provider (SP) application. (On this site and elsewhere the SP is also referred to as the Relying Party, as it relies on the IdP in identity federation). The SP system exists outside the IdP’s boundary and control. For example, the SP application could be that of a vendor, who supplies a software feature that has been outsourced to another system. In figure 1 below, the user has clicked on a link (“Check Messages”) in the host application that requires her to open a window from another system.
  • The IdP SAML Service asserts the authenticated user’s identity to the SP SAML Service. In the example below, assume another browser window has been launched and in the background the SAML SSO assertion is occurring from IdP to SP.
  • The SP SAML Service  consumes the asserted identity, sets any context information required for the asserted identity, and allows the user to access the resource(s) available within its system.
SAML SSO diagram

diagram showing basic connectivity flow between Identity Provider and Service Provider

How to code this

Let’s examine the key steps required to build the SP SAML Service. The company who owns the third-party application (SP) could have had its developers “roll their own” SP SAML Service, but several good tools are available for this task. For the code snippets below, the ComponentSpace SAML v2.0 SSO component is used and the example syntax is C# in ASP.NET. Please note: the steps below have been simplified for the sake of tutorial clarity. Things like logging, error handling, and proper code factoring are missing below, but a production implementation would most certainly need these things.

Create an ASP.NET web application with a single .aspx page. We’ll call this page Receiver.aspx. Add a project reference to the ComponentSpace.SAML2 library. The next code block indicates the required using lines that should be added at the top of Receiver.aspx.cs code-behind file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Xml;
using System.Web.UI.WebControls;
using System.Security.Cryptography.Xml;
using System.Web.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using ComponentSpace.SAML2.Assertions;
using ComponentSpace.SAML2.Protocols;
using ComponentSpace.SAML2.Profiles.SSOBrowser;
using ComponentSpace.SAML2.Metadata;

The next block indicates how the page load event handler should be coded:

protected void Page_Load(object sender, EventArgs e)
{
     if (Request.RequestType == "POST")
     {
          string relayState;
          XmlElement samlResponseXml = null;
          ServiceProvider.ReceiveSAMLResponseByHTTPPost(HttpContext.Current.Request, out samlResponseXml, out relayState);
          SAMLResponse samlResponse = new SAMLResponse(samlResponseXml);
          SAMLAssertion samlAssertion = GetAssertion(samlResponse);
          Dictionary<string, string> AssertedData = ExtractAssertedData(samlAssertion);
          BuildFormsAuthTicketAndRedirect(AssertedData);
       }
}

As indicated in the page load event handler, the intent is to process payloads posted to this page via HTTP POST. The assumption is that the IdP SAML Service has been configured to post to the Receiver.aspx page on the SP SAML Service. When that happens, the code in the page load event handler (above) will execute through each line in that method body. The first meaningful line of code in the page load event hander is the call to ComponentSpace’s ServiceProvider static method ReceiveSAMLResponseByHTTPPost (line 7), which extracts the SAML payload as an XMLElement that is passed in to the SAMLResponse constructor (line 8) to create the samlResponse object. The samlResponse object is handed off to the GetAssertion (line 9), which is the next method we will examine in the Receiver.aspx.cs class.

private SAMLAssertion GetAssertion(SAMLResponse samlResponse)
{
    SAMLAssertion samlAssertion = null;

    if (samlResponse.GetAssertions().Count > 0)
    {
        samlAssertion = samlResponse.GetAssertions()[0];
    }
    else if (samlResponse.GetEncryptedAssertions().Count > 0)
    {
        EncryptedAssertion sa = samlResponse.GetEncryptedAssertions()[0];
        XmlElement saxml = sa.ToXml();
        var encdata = saxml["xenc:EncryptedData"];
        var encmethod = encdata["xenc:EncryptionMethod"];
        var algorithm = encmethod.Attributes["Algorithm"].Value;

        EncryptionMethod encryptionMethod = new EncryptionMethod(algorithm);

        /* next three code lines get the cert needed for payload decryption from the
            local machine personal cert store, finding it by the certificate authority
            used by the Service Provider */
        X509Store xstore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        xstore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
        X509Certificate2 decryptionCert = xstore.Certificates.Find(X509FindType.FindByIssuerName, "SP Certificate Authority", true)[0];

        samlAssertion = sa.Decrypt(decryptionCert, encryptionMethod);

    }
    else if (samlResponse.GetSignedAssertions().Count > 0)
    {
        XmlElement samlAssertionElement = samlResponse.GetSignedAssertions()[0];

        /* next three code lines get the cert needed for signature verification from the
        local machine personal cert store, finding it by the certificate authority
        used by the Identity Provider */
        X509Store xstore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        xstore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
        X509Certificate2 sigCert = xstore.Certificates.Find(X509FindType.FindByIssuerName, "IdP Certificate Authority", true)[0];

        if (!SAMLAssertionSignature.Verify(samlAssertionElement, sigCert))
            throw new ArgumentException("The SAML assertion signature is invalid");

        samlAssertion = new SAMLAssertion(samlAssertionElement);
    }

    return samlAssertion;
}

The GetAssertion method deserves some scrutiny. There are three branches to the IF statement in this method, one branch for each type of assertion. Typically, an Identity Provider will post a SAML payload with only one type of assertion, but the method above handles these three types just to show the options available with how assertions can be conveyed in the SAML payload from Identity Provider to Service Provider. The three ways these assertions can be contained are as follows:

  1. Unencrypted, unsigned. This type of assertion is handled in line 5 above.
  2. Encrypted assertions. This type of assertion is handled in line 9 above. The encryption is done by the Identity Provider using the public key of a certificate held by the Service Provider. Because the Service Provider possesses the complete certificate, this company also holds the private key, which is required to perform payload decryption. The code above accesses the certificate from a certificate store on the computer where the SP SAML Receiver is installed.
  3. Signed assertions. This type of assertion is handled in line 29 above. Another way to secure the SAML assertions from the Identity Provider is for the IdP to sign the assertions with their private key. The IdP has also provided the signing certificate’s public key to the Service Provider, who uses that to verify the authenticity of the signature.

The next method in the execution flow is ExtractAssertedData:

private Dictionary<string,string> ExtractAssertedData(SAMLAssertion samlAssertion)
{
    Dictionary<string, string> AssertedDataDictionary = new Dictionary<string,string>();
    AttributeStatement attributeStatement = (AttributeStatement)samlAssertion.Statements.FirstOrDefault(bbb => bbb is AttributeStatement);
    foreach (SAMLAttribute attribute in attributeStatement.Attributes)
    {
        string key = attribute.Name;
        string value = attribute.Values[0].Data.ToString();
        AssertedDataDictionary.Add(key, value);
    }

    return AssertedDataDictionary;
}

ExtractAssertedData is straightforward in that it merely loads up a dictionary collection with the important asserted information that the Service Provider’s system will require to properly grant access to the user in context. The attributes that can be sent in the SAML assertion will vary based on the implementation. Commonly, one of the attributes will identify the user, either her user name or ID.

Finally, the last method in the Receiver.aspx.cs class will transition the user over to the requested resource in the Service Provider’s system. For this tutorial, the hypothetical user experience is that she wants to access a web page hosted within the Service Provider’s system. Therefore, the BuildFormsAuthTicketAndRedirect method creates a FormsAuth ticket with the ID (line 5) of the user and the requested action (line 9). Once the FormsAuth ticket is loaded into memory, the user is directed to the requested resource (line 13).


private void BuildFormsAuthTicketAndRedirect(Dictionary<string, string> AssertedDataDictionary)
{
    FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
            1,                           // version
            AssertedDataDictionary["USER_ID"],
            DateTime.Now,                // creation
            DateTime.Now.AddMinutes(31), // Expiration
            false,                       // Persistent
            AssertedDataDictionary["ACTION"]);         // Indicates the action requested by the user
    string sAuthTicket = FormsAuthentication.Encrypt(authTicket);
    HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, sAuthTicket);
    HttpContext.Current.Response.Cookies.Add(authCookie);
    HttpContext.Current.Response.Redirect("/App/SomePage.aspx", false);
}

Conclusion

This article has dissected SAML SSO from the perspective of a Service Provider that relies on the Identity Provider to authenticate users. The emphasis has also been on how ComponentSpace’s SAML v2.0 SSO library simplifies the task of handling the SAML messaging. There are other dimensions to SAML SSO, but hopefully the article has helped the reader better understand the cross-system security assertion process.