Introduction
Understanding Page lifecycle is very crucial in order to develop ASP.NET applications. Most beginners tend to get confused while dealing with dynamic controls and face problems like losing values, state etc on postbacks. Since HTTP is stateless, the nature of web programming is inherently different from windows application development, and the Page lifecycle is one of the primary building blocks while learning ASP.NET. The sequence of events, especially while working with MasterPages in ASP.NET 2.0, has become slightly more complex and this article is aims to shed some light on these events by explaining the order and importance of each event.
Background
Whenever the user requests a particular .aspx page in an application, a lot of interesting things happen on the web server where the application is hosted. Understanding this sequence of events will help us to program and respond to events properly and also clear any confusion which generally arises due to the stateless nature of web programming.
Basics: The New Compilation Model and the Partial Classes
Each web form in an ASP.NET application derives directly or indirectly from a System.Web.UI.Page class. A web form has two components: a code behind file (WebForm.aspx.cs) which contains the code for the events and other methods related to a Page, and the designer ASPX file, which contains HTML control declarations and events (in the Visual Studio 2005 Web Application project model, we have a designer class named WebForm.aspx.designer.cs).
In ASP.NET 2.0, we do not need to define the control variables as well as there event handlers in the code behind, thanks to Partial classes. In ASP.NET 1.x, all this code was auto generated and placed in the code behind file under InitializeComponent() section. But in version 2.0, the runtime will create a partial class dynamically from the ASPX page containing all this info and merge it with the code behind partial class. This will help in making the actual code behind class a lot cleaner and more manageable.
Also, this would eliminate the name change related issues which were common in VS 2003 (if we change any control's ID, it had to be changed everywhere and VS used to modify the code many times). All control related events are defined in the ASPX markup code. So having a single place for controls names and event handlers is cleaner and flexible, whereas the previous VS 2003 model was more "brittle".
Real Thing: The Page life cycle
It is very important to know that for each request, the Page class is instantiated everytime from “scratch”. Which means that any values or whatever state it had previously will get lost unless we use one of the various state maintainance mechanisms provided by ASP.NET like Application, Session, Cache variables or Cookies.
Side Note: View state in ASP.NET 2.0 has changed and now comprises of two parts: Control State and View state. Refer this article for details:
http://msdn2.microsoft.com/en-us/library/1whwt1k7(VS.80).aspx
Below is the sequence of events which fire up sequentially with explanation on the relative importance with respect to web programming in code behind:
Important Note: All events except the Init() and Unload() are fired from outermost to the innermost control. For e.g., a user control’s init event would fire before the Page_Init() event of its parent Page class.
1. PreInit()
In this Page level event, all controls created during design time are initialized with their default values. For e.g., if you have a TextBox control with Text property = “Hello”, it would be set by now. We can create dynamic controls here.
This event occurs only for the Page class and UserControls/MasterPages do not have this method to override. Sample code where you can override this method and add your custom code:
protected override void OnPreInit(EventArgs e)
{
//custom code
base.OnPreInit(e);
}
Note that PreInit() is the only event where we can set themes programmatically.
Special Case with MasterPages
It is important to note that Master Page is treated like a control in the Content Pages.So if a Page has a Master Page associated with it, then the controls on the page will not be initialized and would be null in this stage. Only after the Init() event starts, you can access these controls directly from the page class. Why?
The reason being that all controls placed in the Content Page are within a ContentPlaceholder which is a child control of a MasterPage. Now Master Page is merged and treated like a control in the Content Pages. As I mentioned earlier, all events except the Init() and Unload() are fired from outermost to the innermost control. So PreInit() in the Page is the first event to fire but User Controls or MasterPage (which is itself a Usercontrol) do not have any PreInit event . Therefore in the Page_PreInit() method, neither the MasterPage nor any user control has been initialized and only the controls inside the Page class are set to their default values. Only after the Page_PreInit() event the Init() events of other controls fire up.
See the diagram below showing control hierarchy after the Page_Init() event:
2. OnInit()
In this event, we can read the controls properties (set at design time). We cannot read control values changed by the user because that changed value will get loaded after LoadPostData() event fires. But we can access control values from the forms POST data as:
string selectedValue = Request.Form[controlID].ToString();
3. LoadViewState
This will only fire if the Page has posted back (IsPostBack == true). Here the runtime de-serializes the view state data from the hidden form element and loads all controls who have view state enabled.
4. LoadPostBackData
Again, this method will only fire if the Page has posted back.In this event the controls which implement IPostBackDataHandler interface gets loaded by the values from the HTTP POST data. Note that a textbox control does not gets its value from the view state but from the post data in the form in this event. So even if you disable view state for a particular control, it can get its value from the HTTP POST data if it implements IPostBackDataHandler interface.
Also, an important point to note is that if we have a DropDownList control and we have dynamically added some items to it, the runtime cannot load those values unless the view state is enabled (even if the control derives from IPostBackDataHandler). The reason being that HTTP Post data has only one value per control, and the entire value collection is not maintained in the PostData but in view state.
5. Page_Load
This is the most popular method and the first one for all beginner developers to put their code. Beginners may also think that this is the first method which fires for a Page class. This can lead to a lot of confusion which makes understanding the Page lifecycle all the more important.
Note: If the page has any user control, then it's Load method will fire after the Page class's Load method. The reason as explained earlier is the fact that all method except the Init() are fired from the outermost control to the innermost. So after Page_Load(), load methods of all other controls are fired recursively.
6. Control Event Handlers
These are basically event handlers (like Button1_Click()) which are defined for controls in the ASPX markup. Another source of confusion arises when the developer thinks that an event handler like Button_Click() should fire independently (like in windows apps) as soon as he clicks a Button on the web form, forgetting that Page_Load will fire first before any event handlers.
7. PreRender
This event is again recursively fired for each child controls in the Page. If we want to make any changes to control values, this is the last event we have
to peform the same.
8. SaveViewState
Here, the ViewState of the controls gets saved in the form's hidden control.
9. Render
In this method all controls are rendered recursively (i.e. Render method of each control is called).
10. Unload
Here you can have the page and controls perform clean-up operations. This event has no relevance besides clean up operations because the Page has already rendered.
Dynamic Controls
Now we have seen the important events in the Page lifecycle, let's focus on how to create and maintain state of dynamically generated controls. Many times we need to generate controls dynamically for specific business use cases. For example, I was managing a famous hotel reservation website project and one of my team members was facing an issue in handling the Reservation screen. There was a TextBox where the user enters the number ofrooms, and based on that value, dynamic usercontrols having a room's detailed info were created at runtime.
The developer complained that although he was able to generate user controls as runtime in a for loop, but was unable to save their state. When I looked into the code, I noticed that the code to generate controls was written in a Button's Click event handler. Now as we dicussed above, event handlers like Button_Click() fire much later than LoadViewState() and LoadPostData(), where the control values get loaded from the view state and form's Post data.
So unless he recreates the controls in the Page_Init() or Pre_Init() methods (which occur before LoadViewState and LoadPostData), the control values modified by the user won't get reloaded next time.
Now, when he put the code in the Page_Init() event, he was unable to get the number of rooms entered by the user in the TextBox (which was a static control). The reason being that in Page_Init(), control values are initilized to their design time default values, and do not reflect the user entered values unless they are loaded from the POST data or the view state, a process which occurs later.
So the only way to access the user entered value in the control is to get the value from the form's POST data. Here is the code:
protected override void OnInit(EventArgs e)
{
//get value of the TextBox from HTTP POST data
string selectedValue ;
if(Request.Form["txtNoOfRooms"] != null) selectedValue = Request.Form["txtNoOfRooms"].ToString();
//code to create controls dynamically...
...............
base.OnInit(e);
}
Note: Thanks to Mike Banavige of ASP.NET forums, I added this section. If you create a dynamic control in the Page_Load event, and add it to a PlaceHolder or Panel (with view state turned on), then this dynamic control will maintain its state even though it was not created in the Page_Init(). Why?
The reason is the fact that once a control is added to the control tree of the page, TrackViewState() method is responsible for tracking the state. This method gets fired automatically whenever the control is added to the control tree. Due to this reason, any modifications to the control (like adding items etc) should be done *only after* the dynamic control has been added to the control tree of the Page class, else the state would be lost. See the code below:protected void Page_Load(object sender, EventArgs e)
{
//create a dynamic dropdown
DropDownList d = new DropDownList();
PlaceHolder1.Controls.Add(d); // TrackViewState() gets fired for our dropdown, so state is maintained
if (!IsPostBack)
{
d.Items.Add("test1");
d.Items.Add("test2");
}
}This will not work:protected void Page_Load(object sender, EventArgs e){//create a dynamic dropdownDropDownList d = new DropDownList();if (!IsPostBack){d.Items.Add("test1");d.Items.Add("test2");}PlaceHolder1.Controls.Add(d); //"test1" and "test2" values are lost}
Summary
I have tried to explain relevant events in the Page lifecycle and their importance with some gotchas. I will keep updating this article with more tips and tricks, besides readers are welcome to point out mistakes and suggest corrections and give feedback!
An important thing to remember here is that the entire lifecycle is repeated on every request. The Page class gets re-instantiated and after serving the request gets unloaded. Once we are clear with the sequence of the events, we can structure our code well to suit different use case requirements.
Do you want to hide your quick launch Bar in MOSS 2007
There are two ways to hide the quick launch bar1. You can edit directly in CSS2. Adding Hidden content editor webpartAdd new content editor webpartClick Source Editor in ToolPanepaste the below code in content editor webpart
[style].ms-quicklaunch{ display:none;}.ms-navframe{ display: none;}[/style]
Use "<" & ">" instead of "[" & "]"Set the Hidden Property to True under layout section in ToolPane.Now Refresh the Page......You Quick Launch Bar is Gone :-)
HOW TO: Programmatically customize site navigation in WSS 3.0 and MOSS 2007
Note: This is a repost of an article I posted while WSS 3.0 and MOSS 2007 were in the Beta stages. The article was lost due to a server crash and many people have asked me to repost the article so I updated it with additional information, updates screen shots, and a handy little Windows Forms Application you can use to test these concepts out yourself, or build upon the code base to meet your own needs. Enjoy!
In the previous version of SharePoint the QuickLaunch menu was controlled via the ONET.XML file and ASPX pages that made up a Site Definition, and making changes to the QuickLaunch menu was a cumbersome task at best.
The new version of SharePoint provides significant improvements to the navigational elements within a SharePoint site as well as the ability to change these elements programmatically! This article describes how to programmatically interact with the navigation elements via the WSS API in order to customize SharePoint site navigation. This article also investigates the security trimming capabilities of the SharePoint UI with respect to programmatically edited navigation links.
The first thing to note is that the objects used to programmatically customize navigation reside in the Microsoft.SharePoint.Navigation namespace. So, you’ll need to add a reference to the Microsoft.SharePoint.dll in your Visual Studio projects.
New item: Controlling navigation via the SPWeb object!
The SPWeb object has been enhanced in MOSS 2007 to support the ability to control the navigation of a SharePoint site programmatically! Let’s examine how this can be done.
The SPWeb object has a new property named Navigation that returns a SPNavigation object. This object allows developers to programmatically control the navigation of a SharePoint site.
Shared Navigation Settings
One of the new settings you may apply to a site’s navigation structure is whether or not the site uses the navigation menus from its parent site.
These examples assume you have created a top level site named test and a sub site named sharednav under the test top level site as well as a sub site named nosharednav under the test top level site. Once these sites have been created their hierarchy and URLs will look like this:
Before we get started, to help set the stage, here is what the navigation looks like in the test site.
Here is an example of how to set a sub site to use the navigation from its parent site.
SPSite sharedNavSite = new SPSite(“http://server/test/sharednav”);
SPWeb sharedNavWeb = sharedNavSite.OpenWeb();
sharedNavWeb.Navigation.UseShared = true;
The navigation for the sharednav site looks like this after the lines of code above have been run to set the property. Notice the breadcrumb trail at the top of the page uses the breadcrumb trail for the test top level site and the horizontal menu bar uses the items from the test top level site as well.
Here is an example of how to set a sub site not to use the navigation from a parent’s site.
SPSite noSharedNavSite = new SPSite(“http://server/test/nosharednav”);
SPWeb noSharedNavWeb = noSharedNavSite.OpenWeb();
noSharedNavWeb.Navigation.UseShared = false;
The navigation for the nosharednav site looks like this after the lines of code above have been run to set the property. Notice the breadcrumb trail at the top of the page uses the breadcrumb trail for the nosharednav sub site and the horizontal menu bar uses the items from the nosharednav sub site.
QuickLaunch Menu Items
The QuickLaunch navigation menu may also be accessed programmatically. You can create new menu items in the QuickLaunch navigation menu and remove them. You can also specify if the link is external to the site.
Here is how you add a menu item to the QuickLaunch navigation menu.
These QuickLaunch examples assume you have created a top level site named quicklaunch.
Once this top level site has been created its URL will look like this: http://server/quicklaunch
In this example we will add two links to the QuickLaunch menu for the quicklaunch top level site, one link will be internal and one will be external. The internal link will point to the Links list in the QuickLaunch site. The external link will point to the SharePoint Experts web site.
SPSite quickLaunchSite = new SPSite(“http://server/quicklaunch”);
SPWeb quickLaunchWeb = quickLaunchSite.OpenWeb();
SPNavigationNodeCollection quickLaunchNodes = quickLaunchWeb.Navigation.QuickLaunch;
SPNavigationNode internalMenuItem = new SPNavigationNode(“Links”, “Lists/Links/AllItems.aspx”, false);
quickLaunchNodes.AddAsFirst(internalMenuItem);
SPNavigationNode externalMenuItem = new SPNavigationNode(“SharePoint Experts”, “http://www.SharePointExperts.com”, true);
quickLaunchNodes.AddAsFirst(externalMenuItem);
quickLaunchWeb.Update();
*Note: If you do not call the Update() method on the SPWeb object you are working with, you will need to reset IIS to see your changes.
Here is what the quicklaunch Site Collection looks like after the above code has been executed:
Here is how you remove a menu item from the QuickLaunch navigation menu.
In this example we will remove the external link to the SharePoint Experts web site.
SPSite quickLaunchSite = new SPSite(“http://server/quicklaunch”);
SPWeb quickLaunchWeb = quickLaunchSite.OpenWeb();
SPNavigationNodeCollection quickLaunchNodes = quickLaunchWeb.Navigation.QuickLaunch;
quickLaunchNodes.Delete(quickLaunchNodes([0]));
quickLaunchWeb.Update();
Here is what the quicklaunch Site Collection looks like after the above code has been executed:
Taking the QuickLaunch menu one step further
The QuickLaunch menu is capable of displaying links in a grouped fashion, as you see in the screenshot above. For example, the Lists menu item in the screenshot above has two items under it – the Calendar and Tasks links. Creating your own grouped links can be done programmatically. This is once again a simple process that requires very little code.
Here is how you add grouped menu items to the QuickLaunch navigation menu.
In this example we will create a header link for the group of links and name it Administration. Then we will add two links under the Administration header link. The links will link to the Create and Site Settings pages for the quicklaunch Site Collection.
SPSite quickLaunchSite = new SPSite("http://" + serverTextBox.Text + "/quicklaunch");
SPWeb quickLaunchWeb = quickLaunchSite.OpenWeb();
SPNavigationNodeCollection quickLaunchNodes = quickLaunchWeb.Navigation.QuickLaunch;
SPNavigationNode internalMenuItem = new SPNavigationNode("Administration", "", false);
quickLaunchNodes.AddAsFirst(internalMenuItem);
SPNavigationNode externalSubMenuItem1 = new SPNavigationNode("Site Settings", quickLaunchWeb.Url + "/_layouts/settings.aspx", true);
quickLaunchNodes[0].Children.AddAsFirst(externalSubMenuItem1);
SPNavigationNode externalSubMenuItem2 = new SPNavigationNode("Create", quickLaunchWeb.Url + "/_layouts/create.aspx", true);
quickLaunchNodes[0].Children.AddAsFirst(externalSubMenuItem2);
quickLaunchWeb.Update();
Here is what the quicklaunch Site Collection looks like after the above code has been executed:
*Note: I added the Create and Site Settings links to the QuickLaunch bar to test the security trimmed aspects of the SharePoint UI. I wanted to see if SharePoint would hide these links from users who did not have access to see them because MOSS 2007 hides these links in the out of the box SharePoint UI from users who only have reader access.
I created a user named reader and granted the user read only access to the quicklaunch top level site. When I logged into the quicklaunch top level site with this user the links I just added to the QuickLaunch were available to the reader user! This is an important thing to note, and should be kept in mind when adding links to the QuickLaunch menu!
Here is what the quicklaunch Site Collection looked like when the reader user logged in:
Here is the screen you see when you click one of the links you do not have access to:
Top Navigation Menu Items
The top navigation menu may also be accessed programmatically. You can create new menu items in the top navigation menu navigation menu and remove them. You can also specify if the link is external to the site.
Here is how you add a menu item to the top navigation menu.
This example assumes you have created a top level site named topnavigation.
Once this Site Collection has been created its URLs will look like this: http://server/topnavigation
In this example we will add two links to the top navigation menu for the topnavigation top level site, one link will be internal and one will be external. The internal link will point to the Links list in the topnavigation site. The external link will point to the SharePoint Experts web site.
SPSite topNavigationSite = new SPSite(“http://server/topnavigation”);
SPWeb topNavigationWeb = topNavigationSite.OpenWeb();
SPNavigationNodeCollection topNavigationNodes = topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode internalMenuItem = new SPNavigationNode(“Links”, “Lists/Links/AllItems.aspx”, false);
topNavigationNodes.AddAsFirst(internalMenuItem);
SPNavigationNode externalMenuItem = new SPNavigationNode(“SharePoint Experts”, “http://www.SharePointExperts.com”, true);
topNavigationNodes.AddAsFirst(externalMenuItem);
topNavigationWeb.Update();
Here is what the topnavigation top level site looks like after the above code has been executed:
Here is how you delete a menu item from the top navigation menu. In this example we will remove the Links link we just added to the top navigation menu for the topnavigation top level site.
SPSite topNavigationSite = new SPSite(“http://server/topnavigation”);
SPWeb topNavigationWeb = topNavigationSite.OpenWeb();
SPNavigationNodeCollection topNavigationNodes = topNavigationWeb.Navigation.TopNavigationBar;
topNavigationNodes.Delete(topNavigationNodes[0]);
topNavigationWeb.Update();
Here is what the topnavigation top level site looks like after the above code has been executed:
Taking the Top Navigation one step further
The top Navigation menu is capable of displaying dropdown menus that consist of multiple items. Adding additional sub menu items under the topmost item is a simple process and requires very little code.
Here is how you add a sub menu item to a top level menu item in the top navigation menu. In this example we will add a new top level menu item to the top navigation menu that has two sub menu items under it for the topnavigation top level site.
SPSite topNavigationSite = new SPSite(“http://server/topnavigation”);
SPWeb topNavigationWeb = topNavigationSite.OpenWeb();
SPNavigationNodeCollection topNavigationBarNodes =
topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode headerMenuItem = new SPNavigationNode("SharePoint Sites", "", false);
topNavigationBarNodes.AddAsFirst(headerMenuItem);
SPNavigationNode externalSubMenuItem1 = new SPNavigationNode("SharePoint Experts", "http://www.SharePointExperts.com", true);
topNavigationBarNodes[0].Children.AddAsFirst(externalSubMenuItem1);
SPNavigationNode externalSubMenuItem2 = new SPNavigationNode("SharePoint University", "http://www.SharePointU.com", true);
topNavigationBarNodes[0].Children.AddAsFirst(externalSubMenuItem2);
topNavigationWeb.Update();
Here is what the topnavigation top level site looks like after the above code has been executed:
*Note: I was curious to see if the Top Navigation menu reacted the same way the QuickLaunch menu bar did regarding the security trimming of the UI. So I created an Administration Header link and two sub links for the Create and Site Settings pages.
I granted the reader user read only access to the topnavigation top level site and when I logged into the topnavigation top level site with this user the links I just added to the Top Navigation menu were available to the reader user! This is an important thing to note, and should be kept in mind when adding links to the Top Navigation menu!
Here is the code I used to add the links:
SPSite topNavigationSite = new SPSite(“http://server/topnavigation”);
SPWeb topNavigationWeb = topNavigationLaunchSite.OpenWeb();
SPNavigationNodeCollection topNavigationBarNodes =
topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode headerMenuItem = new SPNavigationNode("Administration", "", false);
topNavigationBarNodes.AddAsFirst(headerMenuItem);
SPNavigationNode externalSubMenuItem1 = new SPNavigationNode("Site Settings", topNavigationWeb.Url + "/_layouts/settings.aspx", true);
topNavigationBarNodes[0].Children.AddAsFirst(externalSubMenuItem1);
SPNavigationNode externalSubMenuItem2 = new SPNavigationNode("Create", topNavigationWeb.Url + "/_layouts/create.aspx", true);
topNavigationBarNodes[0].Children.AddAsFirst(externalSubMenuItem2);
topNavigationWeb.Update();
Here is what the topnavigation top level site looked like when the administrator or the reader user user logged in; the links appear for both users.
I created a handy little Windows Forms Application you can use to test these concepts out yourself, or build upon the code base to meet your own needs. The application is easy to use, just enter the name of your SharePoint server, then click the buttons from top to bottom and follow along with this article. The application looks like this:
I hope this article and the application come in handy for you! Now, it’s time to go skiing! :)
Introduction
Single sign-on using ASP.NET Forms authentication is not a new topic. If you Google it, you’ll find a fair amount of articles talking about it. However, it does not appear to be easy enough to get a clear picture when you try to implement it. The concepts and implementation details are often scattered around, which requires adequate efforts to put pieces together. In this article, I’ll try to summarize my understandings through literature study as well as my own practice, and hope to make it easy for you if you ever need to implement it.
Cookie and Domain Level
Cookies are restricted to a certain domain level for security reasons. Based on FRC 2901 and 2965, cookies can’t be set to a top-level domain such as .com, .org, .gov, etc. The minimum domain level required is the second. For example, mysite.com (second level) and public.mysite.com (third level) are both fine to set a cookie. The third level domain and beyond are often called “sub domains”. www.myiste.com, public.mysite.com, and art.mysite.com are all sub domains of the second level domain mysite.com. If a domain name ends with a two letter country code, a minimum of third level domain will be required. A cookie set to a second-level domain is visible at all its third-level domains. However, a cookie set to a third-level domain is not visible at its parent second level domain and at other sub domains. If no domain name is specified when a cookie is written, the cookie’s domain attribute defaults to the domain name in which the application resides. For example, if a cookie is set by the page http://public.mysite.com/myapp/defauylt.aspx without specifying a domain attribute, the domain of the cookie will default to public.mysite.com. As a result, the cookie will only be visible at this particular sub domain - public.mysite.com. It is not visible at another sub domain like art.mysite.com or at its second level domain mysite.com.
Forms Authentication Ticket and Cookie
ASP.NET keeps track of a user through a Forms authentication ticket which is a securely encrypted text string. This ticket is passed back and forth between a web server and a client browser to persist the user authentication status. During login, the user’s credentials are first verified against a data store, and then the FormsAuthentication.SetAuthCookie method (or other methods) is called to log the user into the application. This method call generates a Forms authentication ticket and at the same time creates a Forms authentication cookie to hold the ticket. In other words, upon successful authentication, ASP.NET creates a Forms authentication cookie that has the value of the Forms authentication ticket. Both the ticket and the cookie are securely encrypted by the Framework.
Please note that the authentication ticket is generally saved into a cookie unless the ASP.NET application is specified as “cookieless”, in which case, the ticket will be appended to a URL by the Framework. This is beyond the scope of this article.
Single Sign-on Within a Sub Domain
Since the Forms authentication is done through a Forms authentication ticket, if all applications share one authentication ticket, a user will only be required to log in once, thus, single sign-on. Let’s say we have two applications with these URLs:
http://public.mysite.com/app1/default1.aspx
http://public.mysite.com/app2/default2.aspx
As you can see, they are basically two virtual directories under the same sub domain - public.mysite.com. A cookie written by app1 will be visible by app2, and vice versa. However, this does not mean when a user logs into app1, he is automatically logged into app2. The reason is that each ASP.NET application, by default, uses its own encryption keys to create a Forms authentication ticket and cookie. Therefore, the cookie written by app1 can’t be successfully read by app2, even though it is visible to app2.
How can we make the two applications recognize each other’s authentication ticket and cookie? In fact, it is pretty simple in ASP.NET. The only thing that needs to be done is to change the settings in the
decryptionKey="AutoGenerate,IsolateApps"
validation="SHA1"
decryption="Auto"
/>
With these settings, the .NET framework uses the automatically generated validationKey and decrytionKey to create an authentication ticket and cookie. Each application uses different keys. To make multiple applications to share the same authentication ticket and cookie, we just need to set the validationKey and decrytionKey in all the applications to the same values. Similar to the following:
validation="SHA1"
decryption="AES"
/>
To generate the keys, the RNGCryptoSErviceProvider class in the System.Security.Cryptography namespace is utilized, as shown in the following code sample:public String CreateKey(int numBytes)
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[numBytes];
rng.GetBytes(buff);
return BytesToHexString(buff);
}
private String BytesToHexString(byte[] bytes)
{
StringBuilder hexString = new StringBuilder(64);
for (int counter = 0; counter < bytes.Length; counter++)
{
hexString.Append(String.Format("{0:X2}", bytes[counter]));
}
return hexString.ToString();
}
Single Sign-on Within a Second Level Domain
It is common to see a number of different sub domains from the same organization due to marketing or administrative needs, just like the following samples:
http://public.mysite.com/app1/default.aspx
http://art.mysite.com/app2/default.aspx
http://www.mysite.com/app3/default.aspx
They are all third-level domains (sub domains) under the same second level domain mysite.com. Suppose all applications have the machine key properly set up for single sign-on, as discussed in the last section. However, you’ll find out that single sign-on does not work. A user who logs into app1 at public.mysite.com will still be required to log in at art.mysite.com and at www.mysite.com. This is because the authentication cookie created in app1 during login has, as a default, the domain attribute of public.mysite.com, which makes it not visible at both art.mysite.com and www.mysite.com.
To make the authentication cookie visible at all other sub domains, we need to modify the cookie’s domain attribute to point to its second-level domain – mysite.com. The following code does just that. This modification makes the single sign-on within the second level domain, mysite.com, a reality.//call SetAuthCookie method to log in. A forms authentication cookie is created.
// Domain name in the cookie defaults to the subdomain where the application resides
FormsAuthentication.SetAuthCookie(txtUserName.Text, false);
//modify the Domain attribute of the cookie to the second level domain
System.Web.HttpCookie MyCookie =
System.Web.Security.FormsAuthentication.GetAuthCookie(User.Identity.Name.ToString(),
false);
MyCookie.Domain = “mysite.com”;//the second level domain name
Response.AppendCookie(MyCookie);
Run Sample Applications
The sample application download (the link at the top of the article) includes two applications: CookieLevel2Domain and CookieSubDomain. As the names implies, the former creates an authentication cookie to a second level domain while the latter to a third level domain. The applications were created in Visual Studio 2008 and Framework 3.5. Since very little coding is involved, you can easily recreate the applications in Visual Studio 2005, if needed. The default.aspx page simulates the authentication process while the GenerateMahineKey.aspx page provides a utility to create the validationKey and decrytionKey that are needed in web.config.
To see how the single sign-on works in relation to the domain level, you need to install each application on to a different sub domain, like:
http://public.mysite.com/CookieLevel2Domain/ProtectedPage.aspx
http://art.mysite.com/CookieSubDomain/ProtectedPage.aspx
Please change the appSetting key - Level2DomainName in the web.config of the first application to the actual second level domain name on your server. After the two applications are set up, browse to the ProtectedPage.aspx in one of them to start an authentication process.
You’ll see that when you log in through the first app, CookieLevel2Domain, you are not required to log in again in the second app, CookieSubDomain. This is because the domain attribute of the Forms authentication cookie is modified to point to the second level domain – mysite.com. However, if you log in through the second app, you’ll be asked to log in again when accessing the first app, since the authentication cookie defaults to the sub domain art.mysite.com and is not visible at the sub domain public.mysite.com.
If you test the two applications on your local development machine, you’ll not have the same experience described above. This is because both applications run at the same domain localhost.
Single Sign-on Cross Domains and Platforms
Single sign-on discussed above is limited to ASP.NET applications within a second level domain. Let’s look at the two ASP.NET applications shown below:
http://public.mysite.com/app1/default.aspx
http://art.yoursite.com/app2/default.aspx
The above applications reside at two different second level domains, mysite.com and yoursite.com, respectively. The single sign-on implemented in the above section will not work simply because of the invisibility of the Forms authentication cookie between the two domains.
Furthermore, if two applications run on different platforms, like PHP, CFM, JSP etc., as shown below:
http://public.mysite.com/app1/default.cfm (Coldfusion app)
http://art.yoursite.com/app2/default.aspx (ASP.NET app)
the single sign-on solution discussed above becomes not applicable.
To achieve single sign-on under these conditions, we may, sometimes, need to look into commercial solutions, for example, the Microsoft SharePoint Server, Microsoft Passport (is this now the Windows Live?), and others. They can do a good job, but also have some drawbacks, such as cost, complexity, privacy issues, etc. Therefore, we sometimes need to come up with a solution of our own.
I presented an article titled Cross Site Authentication and Data Transfer at aspalliance.com. This article discusses the methodology and implementation of single sign-on cross domains or platforms using ASP.NET Forms authentication in conjunction with ASP.NET web services. It also discusses the methodology of transferring user data to an application located at a different domain or platform during the authentication process. Although being highly simplified, it provides a good starting point for developing your own single sign-on authentication solutions.