Introduction
In my previous article, Single Sign-on in ASP.NET and Other Platforms, I mainly discussed single sign-on using forms authentication within a second level domain. As a follow-up, in this article, I’ll present a methodology to cover the single sign-on cross multiple second level domains/platforms. I’ll also discuss how customer data in your database can be transferred to another site during the authentication process. Let’s look at the following two sample URLs:
- Site 1: http://www.authenticationsite.com/app1/default.aspx (ASP.NET site)
- Site 2: http://www.thirdpartysite.net/app2/default.cfm (ColdFusion site)
Site1 is an ASP.NET application located at the domain authenticationsite.com while Site 2 is a ColdFusion application at the domain thirdpartysite.net. Authentication across these two sites can be challenging. First of all, these two sites cannot read each other’s cookie since they are on different second level domains, authenticationsite.com vs. thirdpartysite.net. Secondly, they are running on different platforms, ASP.NET vs. ColdFusion. Each has its own authentication mechanism.
In the following sections, I’ll illustrate the methodology that makes it possible to authenticate a user across domains, and at the same time transfer user data from one site to another.
Basic Program Logic
Create a centralized authentication application that consists of an ASP.NET application and an ASP.NET authentication web service. The user is always brought to this site to sign on. After being successfully authenticated, the user is then redirected to a third party site where the third party application calls the authentication web service to confirm the user’s login status and to retrieve a set of user data to update the third party database. To accomplish this, the user comes to the authentication site through a formulated login link. This link contains two QueryString parameters, SiteID
and ReturnUrl
. It looks like this:
http://www.AuthenticationSite.com/login.aspx?SiteID=1&ReturnUrl=http://www.Third PartySite.net/ LandingPage.aspx?Para1=xxx&Para2=yyy
SiteID
is a parameter that the authentication site uses to determine web page presentation style as well as data transfer logic (what Stored Procedure to use for delivering data to a third party site). ReturnUrl
represents a landing page of a third party site to which the user will be redirected to. Para1
and Para2
are sample parameters as part of the ReturnUrl
, and are used by the third party site for its own purposes. ReturnUrl
should be UrlEncoded()
so that the whole string including parameters and any preserved characters like “?”, “=”, “&” is treated as one single parameter, like the following:
http://www.AuthenticationSite.com/login.aspx?SiteID=1&ReturnUrl= http%3a%2f%2fwww. ThirdPartySite.net%2fLandingPage.aspx%3fPara1%3dxxx%26Para2%3dyyy
Depending on what platform the third party application runs on, the landing page could be implemented in any applicable web technology, including ASP.NET. The authentication site obtains the SiteID
and the ReturnUrl
from the QueryString, and then presents the user, based on the SiteID
, with a login page that has a graphic presentation resembling the third party site. The user enters his username and password, and clicks the Login button. Upon his credentials being successfully verified, the application saves the authentication session data including SiteID
, UserID
, authentication ExpirationDateTime
, and ReturnUrl
(can add more, if needed) into the database, and generates a unique AuthID representing the data saved, and appends the AuthID to the end of ReturnUrl
. ReturnUrl
would now look like:
http://www.ThirdPartySite.net/LandingPage.aspx?Para1=xxx&Para2=yyyy&AuthID=RD69usAsVDrAf56Hdd8R
The user is then redirected to the landing page of the third party site using the above ReturnUrl
.
At the third party site, the landing page grabs the URL parameter AuthID
from the QueryString and calls the authentication web service. The web service retrieves the authentication session data saved earlier, and checks ExpirationDateTime
. A valid ExpirationDateTime
signifies that the user has been authenticated. The web service then retrieves a set of user data from the database using a Stored Procedure determined by the SiteID
, and returns the data to the third party application. The landing page inserts/updates user data in its own database, and performs necessary actions to programmatically log the user into its system.
Run Demo
To illustrate how the cross domain authentication works, I prepared a demo application for download. The solution contains two websites. One is the AuthenticationSite that provides a login interface to authenticate users as well as a web service for a third party to consume. The other is the ThirdPartySite that simulates a third party site. Although both sites are in the same solution and run on a local machine, the methodology is applicable to any site on a different domain/platform.
To run the demo, you’ll need SQL Server 2005 Express Edition (make sure that the service is running). It may be necessary to test its connection from Server Explorer in Visual Studio 2008. Microsoft Enterprise Library for data access is also required. However, the DLLs are included in the demo.
Open the solution in Visual Studio 2008. Expand the ThirdPartySite tree, right click on LandingPage1.aspx, and select View in the browser. When the page comes up, click the “Check Authentication Status” button. You’ll see that the user is not authenticated at this time. Close the browser. This action is to make the ThirdPartySite accessible by starting its local web server since it is a file system based application. Expand the AuthenticationSite tree. Right click the Default.aspx page and select View in the brower. Two links are now shown on the page. Looking at the HTML code for these two links, you'll see that one has a ReturnUrl
that points to the Third Party #1 website and another to the Third Party #2 website. In the demo, they are not really two sites, but two pages, LandingPage1.aspx and LandingPage2.aspx, in the ThirdPartySite application. Please check the port number in the links, and make sure it is the correct number for the ThirdPartySite application.
Click the link "Third Party #1 Web Site", the login.aspx page shows up with a header image and a header text of Site1. Enter user name: johnd, and password: password (or janed/password, there are only two users in the customer table), and click the Login button. You are authenticated and redirected to the LandingPage1.aspx in the ThirdPartySite. The landing page calls the web service and pulls out John Doe’s data, and programmatically logs John into the ThirdPartySite. Click the “Check Authentication Status” button. This time, you’ll see that the user is authenticated. If you change the "AuthID" in the URL or leave the page open for over a minute and then refresh the page, you'll see a message indicating that the URL expired. Repeat the above steps with the Third Party #2 website link on the default.aspx page, you’ll see a different header image and header text, and will be redirected to LandingPage2.aspx after being authenticated.
AuthenticationSite Application
The AuthenticationSite application consists of a SQL Server database, an ASP.NET login page, a master page, an authentication class, a SiteInfo
class, and a web service. Let’s take a closer look at each one of them.
Database
CustomerDB, a SQL database, is the backend data storage for the application. There are three tables, Customer, SiteInfo, and AuthenticationLog, and six Stored Procedures in the database. The Customer table holds the user contact information including login credentials. The SiteInfo table specifies style information for each third party site as well as a Stored Procedure name for pulling a set of customer data for data transfer. The AuthenticationLog table holds each authentication session data as a user logs in at the AuthenticationSite. This data will be retrieved by a third party application through a web service call to confirm a user’s authentication status.
The six Stored Procedures are described here:
- Customer_Login - Verifies user credentials and returns
CustomerID
. More fields may be returned by editing this, if needed. - SiteInfo_GetSiteInfo - Retrieves style information based on
SiteID
, such as style sheet name, header image path, and header text, as well as a Stored Procedure name for data transfer. - Site1_GetCustomerInfo - Pulls a complete set of customer data for the third party #1 site. Note that there is only one
Select
statement in the Stored Procedure in this illustration. However, more statements (with Join
s and conditions) can be added based on business requirements. Multiple Select
statements will return multiple tables in a DataSet
. - Site2_GetCustomerInfo - Retrieves customer data similar to the above for the third party #2 site.
- AuthencationLog_insert – Upon successful authentication, save authentication session data into the AuthenticationLog table. This includes,
SiteID
, UserID
, ExpirationDateTime
, and ReturnUrl
. - AuthencationLog_Get – The web service uses this procedure to retrieve authentication session data and confirm the user authentication status.
Classes
There are two classes in the AuthenticationSite application: SiteInfo
and Authentication
.
The SiteInfo
class assists to set up the presentation of the login page based on SiteID
. This class is straightforward without a need for explanation.
The authentication class does most of the authentication work. The VerifyCredentials
method performs user credential check, and returns the user's identity in a DataTable
. The number of fields returned is based on the stored procedure - Customer_Login. In our example, only UserID
(CustomerID
) is returned. The WellFormReturnUrl
method creates a well formed ReturnUrl
by properly appending an AuthID
to the original ReturnUrl
. The RetrieveUserDataSet
method pulls back a complete set of user data for transferring to a third party. The argument siteID
determines what stored procedure to use. The code in each method is shown below:
public static DataTable VerifyCredentials(string userName, string password)
{
return ExecuteDataSet("Customer_Login",
new object[] { userName, password }).Tables[0];
}
public static string WellFormReturnUrl(string originalReturnUrl,
string authID)
{
string WellFormedUrl = "";
int Position = originalReturnUrl.IndexOf("?");
if (Position != -1)
{
WellFormedUrl = originalReturnUrl + "&AuthID=" +
HttpUtility.UrlEncode(authID);
}
else
{
WellFormedUrl = originalReturnUrl + "?AuthID=" +
HttpUtility.UrlEncode(authID);
}
return WellFormedUrl;
}
public static DataSet RetrieveUserDataSet(int siteID, string userID)
{
DataSet ds = ExecuteDataSet(GetDataTransferProc(siteID), new object[] { userID });
return ds;
}
The rest of the methods in the Authentication
class do data manipulation and interaction with the database. Here, the authentication session data is first placed into a name value collection using the BuildUserDataCollection
method. This makes it easy to retrieve any name value pair from the authentication session data. The data is then serialized into one single string by calling the SerializeParameters
method, and is saved into the AuthenticationLog table. By using serialization, more data pairs can be added, if needed, without modifying the table structure. Retrieving the authentication session data is a reverse action that creates a user data collection through deserializing the saved data string by calling DeserializeUserData
.
Master Page
The graphic presentation style of the authentication site is achieved using a Master page, AuthMaster.master. The style is dynamically applied based on SiteID
so that the authentication site looks similar to its target third party site. In the demo, the style is relatively simple. However, it does illustrate the methodology for more sophisticated graphic presentation.
There are two style sheets, two header images, and two header texts which match two third party sites in the demo application (actually two landing pages), respectively. The code listed below is the function that applies styles in the AuthMaster.master page. The variable TableWidth
controls the display width of the page, and is directly placed on the master page in the HTML table tag, and therefore, DataBind()
needs to be called to bind the variable. The rest is either an ASP.NET server control (for example, imgHeader
and lblHeaderText
) or HTML server control (for example, MainStyle
). Their properties change with SiteID
.
private void ApplySiteStyle(SiteInfo siteInfo)
{
MainStyle.Href = siteInfo.StyleSheetName;
imgHeader.ImageUrl = siteInfo.HeaderImagePath;
imgHeader.Width = Unit.Pixel((int)siteInfo.HeaderWidth);
imgHeader.Height = Unit.Pixel((int)siteInfo.HeaderHeight);
lblHeaderText.Text = siteInfo.HeaderText;
TableWidth = (imgHeader.Width == 0 ? "800" :
imgHeader.Width.ToString());
DataBind();
}
Login page
The login page basically does three things during a user authentication process.
- Calls the VerifyCredentials method in the
Authentication
class to check user credentials against the database, and returns the UserID
in a DataTable
. The reason that a DataTable
is used here is to give the flexibility that more fields can be returned by modifying the Customer_Login Stored Procedure; - Serialize the authentication session data including
SiteID
, UserID
, ExpirationDateTime
, and ReturnUrl
, and then generate an AuthID and save the information into the AuthenticationLog table. The ExpirationDateTime
is set to be 1 minute from the execution date time, indicating that the AuthID will expire in 1 minute from the time the user credentials are verified; - Append the AuthID to the end of the
ReturnUrl
and redirect the user to a third party site.
The SiteID
and ReturnUrl
are requested in the Page_Load
event, and their values are assigned to two invisible labels, lblSiteID
and lblReturnUrl
(Label.Visible=false
). They are used when the Login button is clicked. The code in the Login button click event is self explanatory, as shown below:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
lblReturnUrl.Text = (Request.QueryString["ReturnUrl"]??"").ToString();
lblSiteID.Text = (Request.QueryString["SiteID"] ?? "").ToString();
if (lblReturnUrl.Text == ""||lblSiteID.Text=="")
{
lblError.Text = "The ReturnUrl or SiteID is missing. Can't proceed.";
btnLogin.Enabled = false;
return;
}
txtUserName.Focus();
lblError.Text = "Login credentials: user: johnd " +
"pw: password or user: janed pw: password";
}
}
protected void btnLogin_Click(object sender, EventArgs e)
{
try
{
DataTable tbl = Authentication.VerifyCredentials(txtUserName.Text,
txtPassword.Text);
if (tbl.Rows.Count>0)
{
int MinutesToExpire = 1;
ProcessAuthenticationData(tbl, MinutesToExpire,
Convert.ToInt16(lblSiteID.Text), lblReturnUrl.Text);
}
else
{
lblError.Text = "No user with these credentials has been found.";
}
}
catch (Exception ex)
{
lblError.Text = ex.Message.ToString();
}
}
private void ProcessAuthenticationData(DataTable tbl,
int minutesToExpire, int siteID, string returnUrl)
{
NameValueCollection UserData = Authentication.BuildUserDataCollection(tbl,
minutesToExpire, siteID, returnUrl);
string UserString = Authentication.SerializeParameters(UserData);
string AuthenticationID = Guid.NewGuid().ToString().Replace("-", "");
Authentication.ExecuteNonQuery("AuthenticationLog_Insert",
new object[] { AuthenticationID, UserString});
Response.Redirect(Authentication.WellFormReturnUrl(lblReturnUrl.Text,
AuthenticationID));
}
Web Service
AuthenticationService.asmx is a web service for the landing page of a third party to consume in order to confirm a user’s authentication status and to transfer user data. Upon receiving the AuthID (Request.QueryString[“AuthID”]
), the third party landing page calls the RetrieveUserDataSet
web method (or any other web method as needed) with the AuthID to retrieve a complete set of user data. During this process, if the AuthID is expired or tampered with, the web service call will fail.
The web service provides three web methods, as described below.
RetrieveUserDataSet
– the AuthID is passed. Upon success, returns a set of user data as a DataSet
. Upon failure, returns null
, and sends back an error message in the reference parameter returnMessage
.RetrieveUserDataXml
- Does the same thing as the above, but returns a set of user data as a serialized XML string.RetrieveUserID
- The AuthID is passed in. Upon success, returns UserID
as a string. Upon failure, returns an empty string, and sends back an error message in the reference parameter returnMessage
. If you do not need user data for transfer, but need only to confirm a user’s authentication status, this is the method to call.
The RetrieveUserDataSet
and RetriveUserDataXml
web methods return exactly the same data. The reason that two methods are exposed here is to accommodate non .NET third party applications in which a DataSet
is not a recognized data type. Similarly, an error message is sent back to the caller through a reference parameter ReturnMessage
, instead of throwing an exception for the same reason. Depending on what platform a third party uses, more variation of methods may be needed to return data in the right formats for a particular third party application to consume. In the demo, only RetrieveUserDataSet
is called. More web methods are listed here to illustrate the flexibility in the program development.
Third Party Site
The application ThirdPartySite simulates a remote third party site as the target that a user is redirected to upon successful authentication. As mentioned before, a landing page at the ThirdPartySite receives an AuthID from the URL, and then consumes the authentication web service which returns a complete set of user data. The third party application then processes the data returned and updates its database, and then programmatically logs the user into its site. If the web service call fails because of either the AuthID being expired or tampered with, an error message is returned to the third party application. As a demo, the user data returned to the third party is not saved into a database, but displayed in a GridView
.
A web reference pointing to AuthenticationSite/AuthenticationService.asmx in the same solution is added to the site and named as AuthenticationService. There are two landing pages in the application, simulating two different third party sites. The code in LandingPage1.aspx and LandingPage2.aspx is exactly the same in the demo app. Therefore, we only need to take a look at LandingPage1.aspx. Looking at the code listed below, the page gets the AuthID from the QueryString, and requests for Parameter1
and Parameter2
, which are used by the third party for its own purpose. For the demo, any code involving Parameter1
and Parameter2
are omitted. The page then declares an instance of the AuthenticationService
and calls the RetrieveUserDataSet
web method. If a DataSet
is returned, signifying the success of the cross domain authentication, this page performs necessary actions to handle the user data, and then logs the user in programmatically. Otherwise, authentication fails, and an error message is displayed.
string AuthenticationID = Request.QueryString["AuthID"];
if (AuthenticationID == null)
{
lblError.Text = "A required parameter is missing from url. ";
return;
}
string p1 = Request.QueryString["Para1"].ToString();
string p2 = Request.QueryString["Para1"].ToString();
AuthenticationService.AuthenticationService AuthService =
new AuthenticationService.AuthenticationService();
string ReturnMessage = "";
DataSet ds = null;
try
{
ds = AuthService.RetrieveUserDataSet(ref ReturnMessage, AuthenticationID);
}
catch (Exception ex)
{
lblError.Text += ex.Message.ToString();
}
if (ReturnMessage != "")
{
lblError.Text += ReturnMessage;
return;
}
if (ds != null)
{
FormsAuthentication.SetAuthCookie("LoginUser", false);
gvUserData.DataSource = ds.Tables[0];
gvUserData.DataBind();
}
else
{
lblError.Text += ReturnMessage;
}
Summary
I have discussed the methodology for cross domain/platform authentication and data transfer. The demo application is a greatly simplified version in order to illustrate the concept. In real world, at least several additional parts have to be in place, such as “Remember Me”, “Forgot My Password”, “Sign up” etc. In addition, the accessibility of the web service should be restricted to third parties that you have a partnership with. Depending on the programming platform that a third party uses, more variations of a web method may be required to return data in an appropriate format (for example, a string array, or a special character like "|" separated string, etc.) that the third party can process.
This article is based on my previous work published on aspalliance.com, Cross Site Authentication and Data Transfer. Since then, passing a long encrypted data string through URL is no longer used, but a database table is created to keep track of user authentication session data. This approach reduces the complexity of program implementation, and minimizes the possible side effects related to encryption and long URL.
Reference