Mukarram Mukhtar

Single Sign On (p 1)

What is Single Sign On?

In very simple words, Single Sign On is the ability to use services of multiple websites by signing in only single time. Imagine a cluster of websites, either they belong to one company or to different companies but partners; each website requires user authentication before anyone could navigate through it. However, they also want member users of their partners to freely browse through their websites. So this means that one website will authenticate a user, and then pass her user credentials to the other partner website so that she can freely browse, right? Wrong! What will actually happen is, one website will authenticate a user, and then pass, what we call, a SAML Assertion to the other partner website, this SAML Assertion will tell the partner site that “The user I’m passing to you is a good person, treat her well”. The partner website will read this message and create all the required session variables and authentication settings and thus user will seamlessly keep on browsing from one partner website to the other and so on. Okay, enough with the definition let me quickly explain under what scenarios you should implement SSO. Well, I’ve already explained this, either your company has multiple websites, or your company has some partners, and they want their users to freely navigate through their websites, SSO is the right choice!

Single Sign On (SSO) in .NET:

So as you might have already expected, in this demo, we’ll be creating at least 2 websites, we’ll create some dummy information e.g. demographics, in one website and pass it to the other. The website that passes the information is called Identity Provider, and the website that receives the info is called Service Provider. That’s pretty much standard, so we’ll follow it. Okay, so let’s create our Identity Provider website first.

Creating Identity Provider:

Before we can actually start creating the project we need to download Component Source’s class libraries that implement SAML SSO protocol. The libraries can be downloaded from here:

http://www.componentsource.com/products/componentspace-saml2-component/index.html

Download evaluation version and get started. After you download the right version for your computer and install it successfully (installation process should be really very straight forward), you are ready to create Identity Provider website. In addition to the SAML libraries, there is one more thing that I want to mention in this section, i.e. when Identity Provider passes some information to Service Provider, it doesn’t just want to simply pass it, rather, in order to thwart any hacker’s efforts of gaining access to confidential information, it wants to encrypt the data first and then hand it to Service Provider. To achieve this, we’ll use public certificate files. How public certificates work is beyond the scope of this article and needs a whole chapter on data security, so we’ll skip on that and simply get the certificate file and add it into our project. Follow the steps given below to start creating Identity Provider website:

  • Start Microsoft Visual Studio 2005 or later.
  • Click Create New Project
  • In New Project dialog box, select Visual C# in Project types panel on left.
  • Select ASP.NET Web Application in the Templates panel on right.
  • Give a decent physical path and name to your project and select OK.

By doing this, IDE will create a web application project with a default form added into it.

  • The first thing you need to do is, copy ComponentSpace.SAML2.dll to bin directory of this web app. ComponentSpace.SAML2.dll will be found at the following location, if you selected default options during installation; if you did not, then please help yourself and locate where you installed SAML:

C:\Program Files\ComponentSpace\SAML v2.0 for .NET

  • After you copy ComponentSpace.SAML2.dll into your bin directory, add reference to this dll into your project.
  • The certificate file, as explained above, should be available at the following location, copy this file and paste it on the root of your project, then, include it in the project:

C:\Program Files\ComponentSpace\SAML v2.0 for .NET\Examples\SSO\IdP-Initiated\SAML2IdP\idp.pfx

  • Add System.Security in the references of the project.
  • Next, rename your Form1.cs to frmSendData.aspx, right click this page and make it a Startup page of the application.
  • Right click the project node in solution explorer and add Global.asax.
  • Lastly, make the project run from IIS, instead of IDE. For more details on how to do this, get help from this link

After completing the above steps your Solution Explorer should look something like this:

Now we are ready to start coding, let’s start with Global.asax. Following is the code that we’ll put in Global.asax.cs (line numbers are given for explanation and are not part of original code):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Configuration;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace prjIdentityProvider1
{
    public class Global : System.Web.HttpApplication
    {
        // path and name of the free certificate file provided by Component Space
01        private const string idpCertificateFileName = "idp.pfx";       
        // hard coded password of certificate file
02        private const string idpPassword = "password";
        // the name by which we'll store certificate in application level variable
03        public const string IdPX509Certificate = "idpX509Certificate";
        // since it's a test application, we'll allow all certificates
04        private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }
        // this method creates an X509Certificate2 object and returns to the caller method,
        // where it is stored in an application level variable
05        private static X509Certificate2 LoadCertificate(string fileName, string password)
        {
            // if file is not found, raise an exception
            if (!File.Exists(fileName))
            {
                throw new ArgumentException("The certificate file " + fileName + " doesn't exist.");
            }
            try
            {
                // if file is found instantiate the object and return it
                return new X509Certificate2(fileName, password, X509KeyStorageFlags.MachineKeySet);
            }
            catch (Exception exception)
            {               
                throw new ArgumentException("The certificate file " + fileName + " couldn't be loaded - " + exception.Message);
            }
        }
        // on application start create X509 certificate object and store it in application
        // level variable
        protected void Application_Start(object sender, EventArgs e)
        {           
            ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
            // create the file name using app path and physical file name of certificate
            string fileName = Path.Combine(HttpRuntime.AppDomainAppPath, idpCertificateFileName);
            // instantiate the X509 certificate object and store it in application level variable
06            Application[IdPX509Certificate] = LoadCertificate(fileName, idpPassword);
        }
        protected void Session_Start(object sender, EventArgs e)
        {
        }
        protected void Application_BeginRequest(object sender, EventArgs e)
        {
        }
        protected void Application_AuthenticateRequest(object sender, EventArgs e)
        {
        }
        protected void Application_Error(object sender, EventArgs e)
        {
        }
        protected void Session_End(object sender, EventArgs e)
        {
        }
        protected void Application_End(object sender, EventArgs e)
        {
        }
    }
}

If you have already downloaded the Component Space SAML libraries and you’ve seen sample applications, then you’ll find the above code very similar to them. The reason is obvious, I’ve made very minor changes in that code to adjust it for our example, rest of the things are same. Let me dwell upon some of the lines above that need explanation. Line 01 gives the path and name of the certificate file. Since no path is given and just the name, it means the certificate file should be on the root of the project. Currently we are using sample certificate file which is provided by Component Space for free. In real world you’ll use a machine level certificate instead of file certificate. Line 02 is the password of this certificate file. Again in real world password will not be that simple and it will not hard coded here, rather some better techniques will be used to store password. Line 03 simply gives a name to the X509Certificate object that will be stored in an application level variable. On line 04, we have a method that validates the server certificate, which in current case returns nothing but true, again for simplicity. Line 05 is the method that actually instantiates the X509 certificate class. After instantiation, this object is stored in an application level variable which is done on line 06.

Ok, now let’s start coding our frmSendData.aspx. In this form we’ll put 3 text boxes for Member Id, Name and Phone number. We will also add a button, clicking upon which will take us to the Service Provider website along with 3 pieces of information we entered on the form. Let’s code the aspx page first.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="frmSendData.aspx.cs" Inherits="prjIdentityProvider1.frmSendData" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Single Sign On - Data Sender</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table>
            <tr>
                <td><asp:Label ID="lblMemberId" runat="server" Text="Member Id:"></asp:Label></td>
                <td><asp:TextBox ID="txtMemberId" runat="server"></asp:TextBox></td>
            </tr>
            <tr>
                <td><asp:Label ID="lblName" runat="server" Text="Name:"></asp:Label></td>
                <td><asp:TextBox ID="txtName" runat="server"></asp:TextBox></td>
            </tr>
            <tr>
                <td><asp:Label ID="lblPhone" runat="server" Text="Phone:"></asp:Label></td>
                <td><asp:TextBox ID="txtPhone" runat="server"></asp:TextBox></td>
            </tr>
            <tr>
                <td align="center" colspan="2"><br /><asp:Button ID="btnGoSAML" runat="server" Text="Go SAML"
                        onclick="btnGoSAML_Click" /></td>               
            </tr>
        </table>               
    </div>
    </form>
</body>
</html>

The above code is very straight forward and I think there is no need for explanation. Let’s quickly jump to coding and explanation of the cs file.

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Security;
using System.Web;
using System.Web.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;
using ComponentSpace.SAML2;
using ComponentSpace.SAML2.Assertions;
using ComponentSpace.SAML2.Protocols;
using ComponentSpace.SAML2.Bindings;
using ComponentSpace.SAML2.Profiles.SSOBrowser;
namespace prjIdentityProvider1
{
    public partial class frmSendData : System.Web.UI.Page
    {
        // The parameters identifying the target URL of the SP.
        private const string targetQueryParameter = "target";
01        private const string targetURL = "http://localhost/prjServiceProvider1/Default.aspx";
02        private const string strAssertionConsumerServiceURL = "http://localhost/prjServiceProvider1/frmReceiveData.aspx";
        protected void btnGoSAML_Click(object sender, EventArgs e)
        {
03            if (this.txtPhone.Text.Trim().Length == 0 ||
                this.txtMemberId.Text.Trim().Length == 0 ||
                this.txtName.Text.Trim().Length == 0)
            {
                Response.Write("Please enter valid values in the fields below. All fields are required.");
            }
            else
            {
                try
                {
                    if (string.IsNullOrEmpty(targetURL))
                        return;
                    // Create a SAML response with the user's local identity.
04                    SAMLResponse samlResponse = CreateSAMLResponse();
                    // Send the SAML response to the service provider.
05                    SendSAMLResponse(samlResponse, targetURL);
                }
                catch (Exception exception)
                {
                    Response.Write(exception.ToString());
                }
            }
        }
        // Create an absolute URL from an application relative URL.
        private string CreateAbsoluteURL(string relativeURL)
        {
06            return new Uri(Request.Url, ResolveUrl(relativeURL)).ToString();
        }
        // Create a SAML response with the user's local identity.
        private SAMLResponse CreateSAMLResponse()
        {
            //Trace.Write("IdP", "Creating SAML response");
07            SAMLResponse samlResponse = new SAMLResponse();
08            samlResponse.Destination = strAssertionConsumerServiceURL;
09            Issuer issuer = new Issuer(CreateAbsoluteURL("~/"));
            samlResponse.Issuer = issuer;
            samlResponse.Status = new Status(SAMLIdentifiers.PrimaryStatusCodes.Success, null);
10            SAMLAssertion samlAssertion = new SAMLAssertion();
            samlAssertion.Issuer = issuer;
            //Subject subject = new Subject(new NameID(User.Identity.Name));
            Subject subject = new Subject(new NameID());
            SubjectConfirmation subjectConfirmation = new SubjectConfirmation(SAMLIdentifiers.SubjectConfirmationMethods.Bearer);
            SubjectConfirmationData subjectConfirmationData = new SubjectConfirmationData();
            subjectConfirmationData.Recipient = strAssertionConsumerServiceURL;
            subjectConfirmation.SubjectConfirmationData = subjectConfirmationData;
            subject.SubjectConfirmations.Add(subjectConfirmation);
            samlAssertion.Subject = subject;
11            samlAssertion.SetAttributeValue("MemberId", this.txtMemberId.Text);
            samlAssertion.SetAttributeValue("Name", this.txtName.Text);
            samlAssertion.SetAttributeValue("Phone", this.txtPhone.Text);
            AuthnStatement authnStatement = new AuthnStatement();
            authnStatement.AuthnContext = new AuthnContext();
            authnStatement.AuthnContext.AuthnContextClassRef = new AuthnContextClassRef(SAMLIdentifiers.AuthnContextClasses.Password);
            samlAssertion.Statements.Add(authnStatement);
12            samlResponse.Assertions.Add(samlAssertion);
            return samlResponse;
        }
        // Send the SAML response to the SP.
        private void SendSAMLResponse(SAMLResponse samlResponse, string relayState)
        {
            // Serialize the SAML response for transmission.
13            XmlElement samlResponseXml = samlResponse.ToXml();
            // Sign the SAML response.
14            X509Certificate2 x509Certificate = (X509Certificate2)Application["IdPX509Certificate"];
            SAMLMessageSignature.Generate(samlResponseXml, x509Certificate.PrivateKey, x509Certificate);
15            IdentityProvider.SendSAMLResponseByHTTPPost(Response, strAssertionConsumerServiceURL, samlResponseXml, relayState);
        }
        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

Now let’s see what lines need explanation. Let’s talk about line 01 and 02, SAML protocol needs two URLs, a Target URL and an Assertion Consumer Service URL, the difference is, latter receives the SAML assertion sent by our website, decrypts it, runs any validation checks, if everything looks fine then creates authentication cookies and redirects to the former URL. Line 03 is quite simple; it’s checking if any of the fields were left blank then hold on to SAML Assertion until user provides all of the required info. Line 04 creates an object of SAML Response, we talk about method CreateSAMLResponse() in short. After we have a valid SAML Response object, we pass it to SendSAMLResponse() method, in line 05, along with Target URL. Line 06 simply gets an absolute URL of the current application. As promised above, let’s talk about CreateSAMLResponse() method, line 07 instantiates SAML Response object, this class is part of class libraries provided by Component Source. After the SAML Response object is created, we need to set some of its properties, line 08 sets its first destination page where the message will land first. Line 09 sets its issuer property, which is the current application. Line 10 instantiates the SAML Assertion object. This is the object that will keep our information that we want to pass to the destination. We can send only one SAML Response at a time but a SAML Response can contain multiple SAML Assertion objects. The subsequent lines set different properties of SAML Assertion that are quite self explanatory, e.g. line 11 and next 2 lines are setting attributes like Member Id, Name and Phone. Line 12 adds the Assertion object in Response and now our Response object is ready to be sent. SendSAMLResponse() Method is responsible for sending the SAML Response object to the destination, but it first needs to convert it into XML, line 13 is doing this job. Remember we saved an X509Certificate object in an application level variable? Now it’s time to use it, line 14 creates a new object of X509Certificate class from the application variable. Line 15 finally sends the response to its destination.

Now if you run the application you’ll see a window something like this:

This application is complete, but the demo is not. So far we’ve created a sender application, but where is the receiver? In next part of this tutorial we’ll create Receiver app of Single Sign On.

36 Comments »

  1. Outstanding article! Helped me a lot! Thank you so very much!!

    Comment by Debarupa — May 18, 2011 @ 10:56 pm

    • You’re so very welcome 🙂

      Comment by Mukarram Mukhtar — May 19, 2011 @ 2:35 pm

  2. Hi Mukhtar,

    Such a wonderful article. I was searching nearly 4 days about SAML but today only I just came to know how to work with SAML.

    Thanks a lot.

    Regards,
    Prabhu.P

    Comment by Prabhu — June 3, 2011 @ 12:52 pm

  3. Hi Mukhtar,

    In frmSendData page,
    change the following code
    “http://localhost/prjServiceProvider1/SAML/AssertionConsumerServiceDetails.aspx”;
    to
    “http://localhost/prjServiceProvider1/frmReceiveData.aspx”;

    Regards,
    Prabhu.P

    Comment by Prabhu — June 3, 2011 @ 1:15 pm

    • Good catch, Prabhu. Thanks!

      That must be a copy-paste error 😉

      Comment by Mukarram Mukhtar — June 3, 2011 @ 2:37 pm

  4. Hi Mukhtar,

    Thanks a lot. The article is too good.

    Thanks,
    Naresh

    Comment by Naresh Bachu — November 2, 2011 @ 7:56 pm

  5. Hi Mukhtar,

    Thank you very much for such a wonderful tutorial. Awesome….

    jini

    Comment by jini — November 30, 2011 @ 8:08 pm

    • You are very welcome, Jini! 😉

      Thanks for liking it.

      Comment by Mukarram Mukhtar — November 30, 2011 @ 8:56 pm

      • Hi Mukhtar,
        If you dont mind please send the source code to my emai. jinionnet@in.com

        Thanks,
        jini

        Comment by jini — December 1, 2011 @ 11:21 am

  6. Hi Mukhtar,

    Wonderful article, thanks a lot for posting this. Could you also post an article or send me details about encrypting and decrypting SAML assertions.

    Thanks,
    vingai

    Comment by vinod — December 24, 2011 @ 5:30 am

    • Hi Vinod, I haven’t done encryption and decryption of my own on SAML assertions. I let SAML protocol handle it for me. What you’re talking about, my guess is, double encryption of the assertion, which I don’t know even if it’s recommended or not. SAML’s own encryption is pretty good enough, isn’t it?

      Comment by Mukarram Mukhtar — December 27, 2011 @ 2:59 pm

  7. Hi, Could you send me the code to alan_w_gray@hotmail.com , much appreciated

    Comment by Alan — December 30, 2011 @ 9:25 am

  8. When running the code i am getting the following error on the line numbered 15 in your above example.

    IdentityProvider.SendSAMLResponseByHTTPPost(Response, strAssertionConsumerServiceURL, samlResponseXml, relayState);

    the error recieved is Argument 1: cannot convert from ‘System.Web.HttpResponse’ to ‘System.Web.HttpResponseBase’

    I am using VS2010 with the .net4 componentspace .dll file.

    Comment by Jeremy — February 13, 2012 @ 5:24 pm

    • Jeremy,

      With this little information about the error, it’s very hard for me to troubleshoot your program remotely. However, I can e-mail you a working copy of the project source. Please let me know if that helps.

      Thanks,
      Mukarram

      Comment by Mukarram Mukhtar — February 13, 2012 @ 7:25 pm

      • Sure that would be helpful bodhi311@gmail.com

        Comment by jeremy — February 13, 2012 @ 10:09 pm

      • Did you ever get this resolved? I’m running into the same error message…

        Comment by jame — June 12, 2012 @ 6:56 pm

      • No, I got busy in life. But you’re right, I should have tried again.

        Comment by Mukarram Mukhtar — June 12, 2012 @ 9:55 pm

      • I figuredout that if you pass “new HttpResponseWrapper(Response)” for the HttpResponseBase parameter you’ll be all set. Likewise for ServiceProvider.ReceiveSAMLResponseByHTTPPost method you can pass “new HttpRequestWrapper(Request)” for the Request param. Hope this helps others that run into this issue.

        Comment by jame — June 13, 2012 @ 1:03 pm

      • Thanks for the tip, Jame!

        Comment by Mukarram Mukhtar — June 13, 2012 @ 9:33 pm

  9. Hello , this is an excellent article.
    is there a way to subscribe to a callback after The SendSAMLResponseByHTTPPost ? in other words if the login fails or there is an error , is there a way to intercept that ?

    Thanks Again
    tshaiman

    Comment by Tomer Shaiman — February 14, 2012 @ 10:35 am

  10. I’m getting the following exception,

    Exception: ComponentSpace.SAML2.SAMLProfileException: Failed to receive SAML response by HTTP post —> ComponentSpace.SAML2.SAMLBindingException: Failed to receive response over HTTP POST. —> ComponentSpace.SAML2.SAMLBindingException: The form is missing the variable SAMLResponse

    Can you please let me know what might be causing this?

    Comment by JR — March 12, 2012 @ 5:28 pm

    • JR,

      I’m getting the same error. Did you find a way out of it? If yes, could you share the solution.

      Comment by BC — March 27, 2012 @ 3:23 pm

      • Hey guys, as much as I would love to help you fixing the errors, it’s really impossible for me to troubleshoot the program remotely. So let me know if you need source code of this project, I would be more than glad to forward it to your e-mail ids.

        Comment by Mukarram Mukhtar — March 27, 2012 @ 8:04 pm

      • Hi BC,

        Before my code was,

        if (samlResponse.GetAssertions().Count > 0) {
        samlAssertion = samlResponse.GetAssertions()[0];

        It turned out that our identity provider was sending signed assertions and so I changed my code to,

        if (samlResponse.GetSignedAssertions().Count > 0)
        samlAssertion = new SAMLAssertion(samlResponse.GetSignedAssertions()[0]);

        Everything is all set now.

        Please go thro’ 9 Extracting SAML Assertions from SAML Response in the Component Space User Guide.pdf. It has it all.

        I hope this resolves your issue. Good luck 🙂

        Comment by JR — March 27, 2012 @ 8:24 pm

  11. Hello Mukhtar,
    Thanks for the article, can you please share the complete source code for the same.

    Comment by Charu — April 12, 2012 @ 5:55 am

  12. Thanks for the excellent article, most especially the explanations. ComponentSpace’s examples were great, but didn’t offer much by way of illustrating what everything does.

    I’m building this in MVC4 on a farmed server, and can’t use Application-level variables. So I rebuilt much of what you have here into a standalone library in a static class. It works great from the IdP side, but once I send nothing else happens. I know the packet is going out, because I can see it in the debugger, but doesn’t seem to be getting a response from the SP site.

    If you have any insight into this, that would be great. If not, would love to get a working copy of your source. Thanks again, cheers.

    Comment by John Fleetwood — May 23, 2012 @ 9:02 pm

    • Thanks for your feedback, John. I’ve e-mailed you the project source code.

      Comment by Mukarram Mukhtar — May 23, 2012 @ 10:20 pm

  13. Do you have any sample code to consume saml at client?

    Comment by Amar — August 24, 2012 @ 12:15 am

  14. Out of the world Explanation ! brilliant ! 🙂

    Comment by Sagar Gopani — January 8, 2013 @ 10:12 am

  15. Our company has 5 different websites developed using different technology( APS.net, PHP,Flex etc). We have some common users ( some have access to 3 sites, some have 2 like that.)
    Can we use this SSO for users to avoid multiple logins to get into their registered sites?

    Comment by Mathew — January 14, 2013 @ 6:17 pm

    • Most definitely, Mathew. That’s exactly what Single Sign On is built for.

      Comment by Mukarram Mukhtar — January 14, 2013 @ 9:20 pm

      • How do we implemet this sso if we have 5 different sites( asp.net,Php,flex..) ?. Some sites have common users.
        So we need to let the user log in only the registered sites and need to by pass login if he logged in any of these registered sites. Please let me know what would be the best approach for this
        to implement SSO. Each site has seperate db for authentication.

        Comment by Mathew — January 15, 2013 @ 3:07 pm

  16. Hey There. I discovered your blog the use of msn. That is a very smartly written article.
    I will be sure to bookmark it and return to learn more of your helpful info.
    Thank you for the post. I will certainly comeback.

    Comment by nokia e5 Specification — January 26, 2013 @ 1:45 am

  17. Usually I don’t publish on blogs, but I want to state that this short article actually pressured me to try and do this! Many thanks, fairly good article.

    Comment by 2 girls teach sex pdf — April 18, 2013 @ 8:36 pm

  18. I was wondering if you ever thought of changing the
    page layout of your blog? Its very well written; I love
    what youve got to say. But maybe you could a little more in the way of content so people could connect with it better.
    Youve got an awful lot of text for only having one or two images.
    Maybe you could space it out better?

    Comment by adhd kendrick lamar Instrumental — April 19, 2013 @ 12:34 am


RSS feed for comments on this post. TrackBack URI

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

Blog at WordPress.com.

%d bloggers like this: