[Dev Tip] Single Sign On (SSO) for cross-domain ASP.NET applications


Introduction

“Yet another Monday morning at your office. Just started to wonder how fast the weekend just passed away, and how hard are the days going to be in this week. You got a mail! Oh no, not a lucrative job offer. It’s just another requirement from your new client. Your client has a number of ASP.NET of sites, and all he wants is to enable his users to log onto all sites just by logging into a single site. And, obviously, log out of all sites by logging out from a single site.

OK, so your client wants a “Single Sign On” implementation. You thought, oh, it’s not that hard. The ASP.NET forms authentication is the solution. It allows to share the same cookie across sites under the same domain using the same configuration key using the <machineKey> element. A quick Google search gives you some pretty fine examples of Single Sign On implementation using <machineKey> in ASP.NET applications. OK, so life is not that hard as a programmer after all.

Hold on. Something just caught your eyes. The requirement says, “your client has a number of sites, but they are not necessarily under the same domain“. You just missed this important point, and, the very first day at your office just started to appear harder to you. It’s not easy to implement a Single Sign On for sites under different domains, for the very fundamental reason that, the cookie of a particular domain cannot be shared with another domain. Who doesn’t know that it’s the cookie that is used to maintain authentication information across different page requests?”

I just depicted a scenario that is pretty common these days. This is an era of web 2.0 and social networking, as they say. Standalone and “island” like systems are very rare these days. You do a tweet from Twitter and update your status at LinkedIn and Facebook at the same time without doing anything else. You write an article on CodeProject and share it on hundreds of sites within seconds. So, it’s pretty natural that you would expect to log onto a site and jump to another related one without having to re-login again, doesn’t matter to you what domain these sites are deployed under.

So I thought, how about developing something that allows to easily implement a Single Sign On (of course, for ASP.NET sites) for cross-domain sites? People may have tried to implement this in many different ways, and commercial solutions are also available upon purchase. But, what if I try to develop something which is simple, free, and most importantly, works.

How authentication works in ASP.NET

Well, this may not be something new to you. But while we try to solve the hardest problems on earth, we often need to go back to the basics, to try to understand how things really work. So, it wouldn’t be bad to revisit the ABS’s of the ASP.NET forms authentication mechanism.

Here is how ASP.NET forms authentication works:

Forms_Authentication.png

Forms authentication in ASP.NET

Sequence of operations:

  • You hit a page in an ASP.NET application which is accessible only to the authenticated users (logged in users) of the application.
  • The ASP.NET runtime looks for a cookie (forms authentication cookie) in the request, and if the cookie is not found, it redirects the current request to the login page (the login page location is configured in theweb.config).
  • You provide the login credentials and hit the “Login” button. Assuming that the credentials are correct, your system validates the credentials successfully against the data storage, sets the user name to theThread.CurrentPrincipal.Identity.Name property, writes a cookie in the Response (along with setting the cookie value with the user information and setting some properties like cookie name, expiration date/time etc.), and redirects to the originally requested page.
  • You hit another page in the application (or, click a link that navigates you to another page), and your browser sends the authentication cookie (with any other cookie that were written by the same site earlier), this time because the browser has already got the authentication cookie in the last response.
  • As usual, the ASP.NET runtime looks for the authentication cookie in the request, and this time it finds the cookie. It checks the cookie properties (like expiration date/time, path etc.), and if not expired already, reads the cookie value, retrieves the user information from the cookie, sets the user name into theThread.CurrentPrincipal.Identity.Name property, checks if the user has permission to access the requested page, and if he/she has permission, executes and serves the page to the browser.

Pretty simple, right?

How authentication works in multiple ASP.NET sites under the same domain

As depicted already, the ASP.NET forms authentication is fully dependent on Cookies. So, if two different sites can share the same authentication cookie, it is possible to make the same user to log onto both sites just by logging onto a single site.

Now, the HTTP protocol says, two different sites can share a cookie if and only if both sites are deployed under the same domain (or, sub-domain). Internally, your browser stores the cookies locally (either in disk or in memory) against the web site’s URL. When you hit subsequent requests to any site, the browser reads those cookies which have matching domain or sub domain names comparing to the currently requested URL and sends those cookies with the request.

So, let’s assume you have two sites as follows:

These two sites share the same host address (same domain mydomain.dom and sub-domain www), and both sites have been configured to use forms authentication for user authentication and authorization. Suppose you just logged onto the http://www.mydomain.com/site1 site, and as described above, your browser now has a forms authentication cookie originated from http://www.mydomain.com/site1.

Now, if you click/hit any subsequent URL of http://www.mydomain.com/site1, the forms authentication cookie will be sent with the request. Why? Because the cookie originally belongs to this site? Yes, but not 100% correct. To be exact, the cookie will be sent because the request URL http://www.mydomain.com/site1 has the same matching sub domain and domain name (host address) http://www.mydomain.com.

So, after you logged onto http://www.mydomain.com/site1, if you hit a URL of http://www.mydomain.com/site2, the forms authentication cookie (or, any other matching cookie) will be sent with the request for the very same reason thathttp://www.mydomain.com/site2 has the same matching host address (http://www.mydomain.com) even if this is a different application (site2). So, it’s obvious that as the same forms authentication cookie can be shared across two different web applications under the same host address, it’s possible to log onto both web applications at the same time just by logging onto a single web application (hence, Single Sign On).

However, ASP.NET does not allow you to automatically implement Single Sign On just by implementing forms authentication for multiple web applications under the same host address. Why? Because, each different ASP.NET web application uses its own encryption keys to encrypt and decrypt cookie data (and other data such as ViewState) for ensuring security. So, unless you specify a single encryption key for each ASP.NET application, cookies will be sent from the browser, but an application won’t be able to read the value of an authentication cookie that is originated from another application.

Specifying a single authentication key is the solution. For each ASP.NET application, you have to use the same<machinekey> element in the web.config, as follows:

<machineKey 
  validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D" 
  decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
  validation="SHA1"
  decryption="AES"/>

If the same machinekey (with validationKey and decryptionKey) is used across all applications under the same domain, each will be able to read cookie values that originate from other applications.

What if two sites have the same domain and different sub domains?

Suppose your sites are deployed under the following host addresses:

  • site1.mydomain.com
  • site2.mydomain.com

These two sites share the same domain (same second level domain mydomain.com), but has differ in their third level domain (different sub domains site1 and site2).

By default, the browser sends cookies for matching host addresses only (matching domain and sub domain). So, the site site1.mydomain.com won’t get any cookie that is originated form site2.mydomain.com (because they don’t have the same host address, their sub domain is different), and hence even if you configure the samemachineKey for these two sites, one site won’t be able to get cookies originated from another.

So, in addition to implementing the same machineKey across all sites, you also need to instruct the ASP.NET runtime to define the domain of an authentication cookie so that the cookie is sent from the browser with any request that has a matching domain name. You need to configure the forms authentication cookie as follows:

<forms name="name" loginUrl="URL" defaultUrl="URL" domain="mydomain.com"/>

So, how to share the authentication cookie across multiple domains?

Well, there is absolutely no way to do that. The fundamental barrier of the HTTP protocol prevents you from sharing a cookie across different domains, primarily for security reasons.

Say, you have the following two sites under two different domains:

Now, if you log onto the http://www.domain1.com site using forms authentication, and hit a URL of http://www.domain2.com, the browser wouldn’t simply send the authentication cookie of domain1.com to domain2.com. There is no built-in mechanism in ASP.NET to implement a Single Sign On across these two different sites.

In order to implement a Single Sign On across these two sites under different domains, there has to be some “workaround” or some implementation model that lets these two sites to access a single cookie via some “indirect” mechanism.

A very basic Cross Domain SSO implementation model

Suppose we have to implement SSO in these sites:

In order to implement SSO across these sites, we need to set an authentication cookie to the client’s browser for all three sites when the user is authenticated in any one of the two sites.

If user1 is authenticated into http://www.domain1.com, the browser will add an authentication cookie in the response before returning a response to http://www.domain1.com. But as we need to be able to log onto http://www.domain2.com andhttp://www.domain3.com, we need to set the authentication cookie in the same client’s browser for http://www.domain2.comand http://www.domain3.com at the same time. So, before returning the response to the browser, http://www.domain1.comwill have to redirect the browser to http://www.domain2.com and http://www.domain3.com for setting the authentication cookie.

The following diagram depicts the basic idea (arrows denote the redirections):

Basic_SSO_model_overview.png

Basic SSO model overview

And the following sequence diagram depicts the detailed idea:

SSO_Basic_Model.png

Sequence diagram of login operation in the basic SSO model

Sequence of operations:

[Status:  browser has no authentication cookie]
[Status:  browser has no authentication cookie]
[Status:  browser has no authentication cookie]
    • User provides login credentials and hits the “Login” button. Browser sends a POST request tohttp://www.domain1.com.
[Status:  browser has no authentication cookie]
    • http://www.domain1.com accepts the login credentials, verifies the credentials against a data storage, and upon success, marks the user as logged in, creates an authentication cookie with the logged in user’s information, and adds to the response.
[Status : browser has no authentication cookie]
[Status : browser has no authentication cookie]
[Status : browser has an authentication cookie for www.domain2.com]
    • http://www.domain2.com immediately redirects to the ReturnUrl address with setting an authentication cookie for http://www.domain1.com by reading the cookie value from the request. Eventually, the authentication cookie is also sent with the response with the redirect command.
[Status : browser has an authentication cookie for www.domain2.com]
[Status : browser has authentication cookie 
          for both www.domain1.com and www.domain2.com].
    • http://www.domain1.com sees that the authentication cookie is already set in the request, and hence instead of returning the login page, it serves the page that is requested.
[Status : browser has authentication cookie 
          for both www.domain1.com and www.domain2.com].

So, if the user hits an authenticated page of http://www.domain2.com in the browser now, as the authentication cookie is already stored in the browser for http://www.domain2.com, it is sent with the request, and http://www.domain2.comretrieves the user information from the authentication cookie, logs in the user, and serves the page output that is requested by the user.

As the browser now has authentication cookie for both http://www.domain2.com and http://www.domain3.com, the user is considered to be logged onto both sites, and hence, Single Sign On implemented in both sites Smile | :)

How about “Single Sign Out”?

If we only had to manage logging onto the sites for users, we would have been done so far. But as part of the Single Sign On, we also have to manage the “Single Sign Out“. That is, when a user signs out of a single site, the user should be marked as signed out of all the sites.

Internally, when the user signs out of a single site (say, http://www.domain1.com), the authentication cookie of all the other sites needs to be removed from the browser (in this case, http://www.domain2.com). A cookie can be removed from the browser by removing the cookie from the response before sending the response to the browser.

To remove all authentication cookies for all sites, the same flow of request-redirect-response described above should be followed, and instead of setting the authentication cookie, this time, the authentication cookie should be removed from the response.

Problem with this SSO model

This model should work fine for two sites. For logging into a site and for logging out of a site, the corresponding site internally should follow a request-redirect-response flow with all other sites (that are under the SSO). Once the user is logged onto a single site, for all other subsequent requests onto any site, there is no request-redirect-response loop because each site has its own authentication cookie stored in the browser.

But, if the number of sites is more than 2, the complexity increases. That is, if the user logs ontohttp://www.domain1.com, this site will internally redirect to http://www.domain2.com and http://www.domain3.com for setting the authentication cookie in the browser, and finally http://www.domain3.com redirects to http://www.domain1.com and serves the originally requested page output to the browser. This will make the login and logout functionality of each site costly and complex. What if there are more than 3 sites? What if we have 20+ sites to integrate under a Single Sign On model which are deployed under different domains? The model is obviously not going to fit.

This model also requires each site to have knowledge about all the other sites which are configured under the same SSO umbrella (because, each site has to redirect to all the other sites for setting the authentication cookie). Also, this would require the user authentication and authorization logic to be implemented on each site.

It’s clearly understandable, as the number of sites increases, this SSO model will become messy, and will lose its acceptance, and this model can’t really be considered a “generic” SSO model for cross-domain applications. So, we need a better model that should be independent of the number of sites to integrate under a Single Sign On umbrella.

A proposed model for Cross-Domain SSO

The previous model was kind of a “mesh-up” where each site had to be redirected to N-1 other sites for setting and removing authentication cookie for signing in and signing out. Each site had to be configured with knowledge of the other N-1 sites, and some complex and expensive login and logout logic had to be implemented.

What if we maintained a single authentication cookie for tracking user authentication for all sites? What about introducing a new dedicated site for authenticating users and setting the authentication cookie? Sounds lucrative.

In order to implement SSO for multiple sites, the user database should be a unified one, and hence, it makes good sense to implement the user authentication and authorization functionality within a dedicated site where each site can access the functionality via a web or WCF service. This allows the sites to get rid of redundant user authentication/authorization functionality. But the most important question is how this dedicated site is going to help implement Single Sign On.

Well, in this model, the browser will not store authentication cookies for each site. Rather, it will store an authentication cookie for only the dedicated site which will be used by the other sites to implement Single Sign On. Let’s call this site http://www.sso.com.

In this model, each and every request to any site (which takes part in the SSO model) will internally be redirected to the SSO site (http://www.sso.com) for setting and checking the existence of an authentication cookie. If the cookie is found, the authenticated pages are served to the browser, and if not, the user is redirected to the login page of the corresponding site.

The following diagram depicts the basic idea:

Proposed_SSO_model_overview.png

A dedicated site (www.sso.com) for managing the authentication cookie in the proposed SSO model

For understanding the model in detail, let’s imagine we are trying to apply this model to implement SSO to the following two sites:

We now have a dedicated site for managing the authentication cookie: http://www.sso.com.

Here is how this model works:

SSO_Proposed_Model_Part_I.png

Sequence diagram of an authenticated page hit without login

Sequence of operations:

SSO_Proposed_Model_Part_II.png

Sequence diagram of login
    • User provides credentials and hits the “Login” button. No postback request is redirected to the SSO site in this model. This time, http://www.domain1.com invokes the web/WCF service of http://www.sso.com to check the provided user credentials, and upon success, returns the user object with a Token property that is generated each time a user logs in (say, a GUID).
    • http://www.domain1.com marks the user as logged in (say, stores the user object in the session), prepares a URL with the user Token, and redirects the current request to http://www.sso.com to set the authentication cookie, with the ReturnUrl parameter value being set to the original request URL.
    • http://www.sso.com checks the incoming request URL, and finds a user Token, with no authentication cookie being available yet. That indicates the user has been authenticated into the client site (http://www.domain1.com), and the authentication cookie now needs to be set in the browser for http://www.sso.com. So, it retrieves the user from the cache using the Token, prepares an authentication cookie with the user info and sets the other cookie properties (expiration date/time etc.), adds the cookie to the response, and finally redirects to the originally requested URL found in the ReturnUrl query string value (a URL of http://www.domain1.com), along with adding the Token value in the query string.
    • Browser gets a Redirect command now with ReturnUrl to http://www.domain1.com, and also gets an authentication cookie from http://www.sso.com. So it stores the authentication cookie for http://www.sso.com and hits a request to the URL at http://www.domain1.com.
    • http://www.domain1.com now sees that a user Token is present in the query string. It validates the user token via a web/WCF service call at http://www.sso.com, and upon validation, executes the originally requested page ofhttp://www.domain1.com and writes the output in the response.

SSO_Proposed_Model_Part_III.png

Sequence diagram of browsing an authenticated page after login
  • User hits an authenticated page URL of http://www.domain2.com now.
  • http://www.domain2.com redirects the request to http://www.sso.com in the client’s browser, setting the ReturnUrlproperty to the originally requested URL of http://www.domain2.com.
  • The browser gets a redirect command to http://www.sso.com. It already has an authentication cookie stored for this site, and hence adds this cookie to the request before sending the request to http://www.sso.com.
  • http://www.sso.com checks the incoming request and looks for any authentication cookie in the request. If finds an authentication cookie this time, and checks whether the cookie has expired. If not, it retrieves the userToken from the cookie and redirects the request to the originally requested page URL athttp://www.domain2.com, setting the user Token in the query string.
  • http://www.domain2.com sees that there is a user Token presented in the query string. So it validates the user token via a web/WCF service call at http://www.sso.com, and upon validation, executes the originally requested page of http://www.domain2.com and writes the output in the response.

To summarize

Wow, sounds a lot of things are happening! Let me summarize those here:

Initially, the browser doesn’t have any authentication cookie for http://www.sso.com. So, hitting any authenticated page in the browser for http://www.domain1.com or http://www.domain2.com redirects the user to the login page (via an internal redirection to http://www.sso.com for checking the existence of the authentication cookie). Once a user is logged onto a site, the authentication cookie for http://www.sso.com is set in the browser with the logged in user information (most importantly, the user Token, which is valid only for the user’s login session).

Now, if the user hits any authenticated page URL of http://www.domain1.com or http://www.domain2.com, the request is internally redirected to http://www.sso.com in the user’s browser, and the browser sends the authentication cookie, which is already set. http://www.sso.com finds the authentication cookie, extracts the user Token, and redirects to the originally requested URL in the browser with the user token, and the originally requested site validates the Tokenand serves the page that was originally requested by the user.

Once the user is logged onto any site under this SSO model, hitting any authenticated page onhttp://www.domain1.com or http://www.domain2.com results in an internal redirection to http://www.sso.com (for checking the authentication cookie and retrieving the user Token) and then serving the authentication page in the browser output.

Traffic

Following are the summary of traffic for each different scenario:

Scenario1: A public page hit

A trip from the browser to the client site + a trip from the client site to the browser

Summary: 1 Request + 1 Response

Scenario2: An authenticated page hit without login

A trip from the browser to the client site (page hit) + a redirect command to the browser and a trip from the browser to the SSO site (redirect to SSO for cookie check) + a redirect command to the browser and a trip from the browser to the client site (redirect to client site without cookie) + a trip from the client site to the browser (login page)

Summary: 1 Request + 2 Redirects + 1 Response

Scenario3: Login

A trip from the browser to the client site (postback) + an invocation of the authentication Web Service (user authentication) + a redirect command to the browser and and a trip from the browser to the SSO site (redirect to SSO with Token) + a redirect command to the browser and a trip from the browser to the client site (setting the authentication cookie) + an invocation of a Web Service method (Token validation) + a trip from the client site to the browser (serve the authenticated page)

Summary: 1 Request + 2 Web Service calls at the server + 2 Redirects + 1 Response

Scenario4: Browse an authenticated page while logged in

A trip from the browser to the client site (URL hit) + a redirect command to the browser and a trip from the browser to the SSO site (redirect to SSO with the authentication cookie) + a redirect command to the browser and a trip from the browser to the client site (checking the authentication cookie) + an invocation of a Web Service method (Token validation) + a trip from the client site to the browser (serve the page)

Summary: 1 Request + 2 Redirect + 1 Web Service call at the server + 1 Response

Scenario5: Logout

A trip from the browser to the client site (click logout link) + a redirect command to the browser and a trip from the browser to the SSO site (instruction to log out) + a redirect command to the browser and a trip from the browser to the client site (after removing the authentication cookie) + a trip from the client site to the browser (serve the login page)

Summary: 1 Request + 2 Redirects + 1 Response

Trade offs

Comparing the basic SSO model and the proposed one, it seems the basic model suits well for 2, or, at most 3 sites. In the case of the basic SSO model, the login and logout functionality will be complex in terms of implementation and execution time, but the subsequent pages would be served in a normal request-response cycle (1 request and 1 response).

However, extending the SSO is difficult (addition of new sites), configuration management is maximum (each site has to manage their own cookie and each has dependency on other sites), and redundant user authentication functionality has to be implemented on multiple sites.

On the other hand, the proposed model does not have any dependency on the number of sites to integrate (you can add as many sites as you want and the SSO model won’t change). The configuration management is minimum for the participating site (no site has to know about the other sites, they need only know about the SSO site), the authentication and cookie management is handed over to a dedicated site (http://www.sso.com), the SSO related functionality can be easily integrated with the site if implemented smartly, and could be extended easily (new sites could be added easily).

However, there is a small performance penalty. Unlike the basic model, each authenticated page access requires3 requests from the browser to the SSO site and the client site (the two additional requests are due to the two internal redirects). Though, the additional two requests take minimum execution time (a blank redirect request, just for setting and checking the cookie), and hence could be considered acceptable considering today’s high Internet bandwidth availablilty.

Implementation of the proposed SSO model

Enough theories, and we should be prepared well enough now to get our hands dirty with some code. Good that I already have done it and have shared it in the next article. The following article would present an actual implementation of the proposed SSO model for Cross-Domain ASP.NET sites, and would point out some implementation challenges and their workarounds:

The implementation

Yes, I have done a sample SSO application based on the proposed model. It’s not just another “Hello world”, it’s a working application that implements SSO across three different sites under three different domains. The hard work is done, and the soft output is, you just need to extend a class for making an ASPX page a “Single Sign On” enabled one in your ASP.NET application. You, of course, have to set up an SSO site and configure your client applications to use the SSO site, that’s all (merely a 10 minute work).

The SSO implementation is based on the following high level architecture:

CrossDomainSSOExample/Proposed_SSO_model_overview.png

Figure : The Single Sign On implementation model

There may be unlimited number of client sites (in our example, 3 sites) which could participate under a “Single Sign On” umbrella, with the help of a single “Single Sign On” server (call this the SSO site, http://www.sso.com). As described in the previous article, the browser will not store an authentication cookie for each different client site in this model. Rather, it will store an authentication cookie for only the SSO site (http://www.sso.com) which will be used by the other sites to implement Single Sign On.

In this model, each and every request to any client site (which takes part in the SSO model) will internally be redirected to the SSO site (http://www.sso.com) for setting and checking the existence of the authentication cookie. If the cookie is found, the authenticated pages for the client sites (that are currently requested by the browser) are served to the browser, and if not found, the user is redirected to the login page of the corresponding site.

How it works

Initially, the browser doesn’t have any authentication cookie stored for http://www.sso.com. So, hitting any authenticated page in the browser for http://www.domain1.com, or http://www.domain2.com, or http://www.domain3.com redirects the user to the login page (via an internal redirection to http://www.sso.com for checking the existence of the authentication cookie). Once a user is logged onto a site, the authentication cookie for http://www.sso.com is set in the browser with the logged in user information (most importantly, the user Token, which is valid only for the user’s login session).

Now, if the user hits any authenticated page URL of http://www.domain1.com, or http://www.domain2.com orhttp://www.domain3.com, the request is internally redirected to http://www.sso.com in the user’s browser and the browser sends the authentication cookie, which is already set. http://www.sso.com finds the authentication cookie, extracts the user Token, and redirects to the originally requested URL in the browser with the user token, and the originally requested site validates the Token and serves the page that was originally requested by the user.

Once the user is logged onto any site under this SSO model, hitting any authenticated page onhttp://www.domain1.com, or http://www.domain2.com, or http://www.domain3.com results in an internal redirection to http://www.sso.com(for checking the authentication cookie and retrieving the user Token) and then serving the authentication page in the browser output.

OK, show me what you implemented

I have developed a sample Single Sign On application that incorporates three different sites (http://www.domain1.com,http://www.domain2.com, and http://www.domain3.com) and an SSO server site (http://www.sso.com). The sample SSO implementation code is available for download with this article. You just need to download and set up the sites as instructed in the next section. Once you are done with that, you can test the implementation in different scenarios.

The following section has step by step instructions to test the Single Sign On functionality in different scenarios, and each testing scenario has a Firebug network traffic information that depicts the total number of requests (including the lightweight redirect requests) and their length in size. The number of redirect requests and their length in size are marked within green for easy understandability.

Scenario1: Before authentication

Hit the following three URLs in the browser in three different tabs of the same browser window.

CrossDomainSSOExample/LoginUrlSSO.png

Three different login screens will be presented in each different tabs, for each different site:

CrossDomainSSOExample/Login1.png

CrossDomainSSOExample/Login2.png

CrossDomainSSOExample/Login3.png

Traffic info

For presenting the login screen, in total, four requests are sent to the servers, among which three are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficLogin.png

Scenario 2: Authentication

Use one of any following credentials in any one of the login screens to log on. Let’s log onto http://www.domain1.comwith user1/123.

Available credentials:

  • user1/123
  • user2/123
  • user3/123

After login, the following screen will be provided for user1 onto http://www.domain1.com.

CrossDomainSSOExample/Home1.png

Traffic info

For login, in total, three requests are sent to the servers, among which two are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficAuthentication.png

Scenario 3: After authentication

As user1 has logged on to http://www.domain1.com, he should be logged onto the other remaining sites:http://www.domain2.com and http://www.domain3.com at the same time, if those sites are browsed in the same window or in different tabs in the same window. Hitting an authenticated page in http://www.domain2.com and http://www.domain3.comshould not present a login screen.

Let’s just refresh the current page at http://www.domain2.com and http://www.domain3.com in their corresponding window (currently, the login screen is being shown in the browser):

CrossDomainSSOExample/Home2.png

CrossDomainSSOExample/Home3.png

You will see that instead of showing the login screen, the authenticated home page is being shown. So, user1 is logged onto all three sites: http://www.domain2.com, http://www.domain2.com, and http://www.domain3.com.

Each home page shows a “Go to Profile Page” link which you can click to navigate to another page. This demonstrates that clicking on hyperlinks and navigating to other pages in the application also works without any problem.

Traffic info

For browsing authenticated pages after login, in total, 3 requests are sent to the servers, among which 2 are redirect requests (marked in green). The redirect request sizes are also very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficAuthentication.png

Scenario 4: Hit the authenticated page on a different session

As expected, the user’s “Sign on” status should only be valid for the current session ID, and any authenticated page URL hit to any one of the three sites will be successful if the URL is hit in the same browser window or in a different tab of the same browser window. But, if a new browser window is opened, and an authenticated URL is hit there, it should not be successful and the request should be redirected to the login page (because that is a different browser session).

To test this, open a new browser window and hit any URL of the three sites that points to an authenticated page (you can copy and paste the existing URL addresses). This time, instead of showing the page output, you will see the request will be redirected to the login page as follows (assuming that you hit a URL of http://www.domain3.com):

CrossDomainSSOExample/Login3.png

Traffic info

For hitting an authenticated page on a different session, in total, 4 requests are sent to the servers, among which 3 are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficLogin.png

Log out

To log out of the sites, click on the “Log out” link of the home page of http://www.domain1.com. The system will log outuser1 from the site http://www.domain1.com and will redirect to the login screen again:

CrossDomainSSOExample/Login1.png

Traffic info

For logging out, in total, 4 requests are sent to the servers, among which 3 are redirect requests (marked in green). The redirect request sizes are very small (in terms of bytes), and are negligible even considering network latency.

CrossDomainSSOExample/TrafficLogout.png

After log out

As user1 is logged out of the site http://www.domain1.com, he should be logged out from http://www.domain2.com andhttp://www.domain3.com at the same time. So, hitting any authenticated page URL of http://www.domain2.com andhttp://www.domain3.com should now redirect to their corresponding login screens.

To test this, refresh the current page of http://www.domain2.com and http://www.domain3.com. Instead of refreshing the page, the system will now redirect the requests to their login pages:

CrossDomainSSOExample/Login2.png

CrossDomainSSOExample/Login3.png

Traffic info

Same as login.

Great… this seems to be working! How do I set up the sites?

The sample SSO implementation has been developed using Visual Studio 2010, .NET 4.0 Framework, and tested in IIS 7 under a Windows Vista machine. However, it doesn’t use any 4.0 framework specific technology or class library and hence, it can be converted to use for a lower level framework without much effort, if required.

Follow these steps to set up the example SSO implementation in your machine:

    • Download SSO.zip and extract to any convenient location in your PC. The following folders/files will be extracted within the folder “SSO“:

    • Click on “SSO.sln” to open the solution in Visual Studio, to understand “Who is What” in the solution structure:

CrossDomainSSOExample/SolutionExplorer.png

As the names imply:

      • C:\…\www.domain1.com is the website for http://www.domain1.com
      • C:\…\www.domain2.com is the website for http://www.domain2.com
      • C:\…\www.domain3.com is the website for http://www.domain3.com
      • C:\…\www.sso.com is the web site for http://www.sso.com
      • SSOLib is a Class Library that handles all the Single Sign On related logic along with communicating with the SSO site via a Web Service, on behalf of the client sites.
    • Type “inetmgr” in the Run command to launch the IIS Manager (alternatively, you can navigate to the IIS Manager from Start->Program files) and create a site there named “http://www.domain1.com“:

Right click on “Sites” and click on “Add Web Site…“:

CrossDomainSSOExample/AddNewSite.png

Provide the necessary inputs in the following input form and click “OK”:

CrossDomainSSOExample/CreateSiteDomain1.png

The site “http://www.domain1.com” will be created in IIS. After creating the site, the site might be shown with a red cross sign in IIS Explorer, indicating that the site is not started yet (this happens in my IIS in Windows Vista Home Premium). In this case, you need to select the site and click on the Restart icon to make sure it starts (the Restart icon is available in the right-middle portion of the screen in IIS Explorer).

CrossDomainSSOExample/RestartSite.png

CrossDomainSSOExample/CreateAllSites.png

    • Click on the “Application Pool” node in IIS Explorer. The application pool listing will be shown in the right pane, and you will see the application pools for the corresponding sites that you’ve just created in IIS.

Make sure all application pools are running under .NET Framework 4.0 (as the web application has been built in Framework 4.0). To do that, right click on the corresponding application pools (that have the same names as the site names) and select the .NET Framework version in the form:

CrossDomainSSOExample/ApplicationPool.png

    • Edit the Hosts file (C:\Windows\System32\drivers\etc\hosts) so that the site names are mapped to the localhost loopback address (127.0.0.1):
127.0.0.1       localhost
127.0.0.1       www.domain1.com
127.0.0.1       www.domain2.com
127.0.0.1       www.domain3.com
127.0.0.1       www.sso.com

If things are correctly done, you should be able to run the sites and test correctly as shown above. Otherwise, please verify if there is any thing missing or misconfigured by reviewing the steps from the start.

OK, how do I implement SSO for my sites?

Good question. The sample SSO implementation works fine. But, as a developer, you would likely be more interested in how to implement SSO in your ASP.NET sites using the things developed. While implementing the SSO model, I tried to make a pluggable component (SSOLib.dll) so that it requires minimum programmatic change and configuration. Assuming that you have some existing ASP.NET applications, you need the following steps to implement “Single Sign On” across them:

    • Add a reference to “SSOLib.dll“, or add a reference to the “SSOLib” project to each ASP.NET application.
    • Set up the SSO site (see previous steps).
    • Configure your ASP.NET applications to use the SSO site. To do this, just add the following configurations in the web.confing of each ASP.NET application:
<!--Configuration section for SSOLib-->
 <configSections>
    <sectionGroup name="applicationSettings" 
         type="System.Configuration.ApplicationSettingsGroup, System, 
               Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <section name="SSOLib.Properties.Settings" 
        type="System.Configuration.ClientSettingsSection, System, 
              Version=4.0.0.0, Culture=neutral, 
              PublicKeyToken=b77a5c561934e089" 
        requirePermission="false" />
    </sectionGroup>
 </configSections>
 <applicationSettings>
    <SSOLib.Properties.Settings>
     <setting name="SSOLib_Service_AuthService" serializeAs="String">
       <value>http://www.sso.com/AuthService.asmx</value>
     </setting>
    </SSOLib.Properties.Settings>
 </applicationSettings>
 <appSettings>
       <add key="SSO_SITE_URL" 
         value="http://www.sso.com/Authenticate.aspx?ReturnUrl={0}" />
       <add key="LOGIN_URL" value="~/Login.aspx" />
       <add key="DEFAULT_URL" value="~/Page.aspx" />
   </appSettings>
<!--End Configuration section for SSOLib-->

Note: Modify the configuration values according to the SSO site URL of your setup and your application specific needs.

  • Modify the code-behind classes (*.aspx.cs) of the login page and other private pages (pages which are accessible only to authenticated users) so that instead of extending System.Web.UI.Page, they will extend the SSOLib.PrivatePage class.
  • In addition to existing codes that perform the log out functionality, call the Logout() method that is already available to the code-behind classes from the SSOLib.PrivatePage base class. Also, instead of executing existing login functionality, call the Login() method of SSOLib.PrivatePage and execute other post-login codes if they exist already.

That’s it! You should be done with your SSO implementation.

Hold on! My pages already extend a base class

OK, there is a high chance that you already have a base class which is extended by the code-behind classes in your ASP.NET applications. If that is so, integrating the SSOLib.PrivatePage may become even easier for you.

Let’s say there is already a BasePage class which is extended by the code-behind classes of the authenticated page (pages which are accessible onto to the authenticated users) in one of your applications. In this case, instead of modifying the code-behind classes of all the ASPX pages, you might just need to modify theBasePage so that it extends SSOLib.PrivatePage, and you are done.

class BasePage : SSOLib.PrivatePage
{
    ...
}

Another alternative is to modify SSOLib.PrivatePage to extend the existing BasePage (you have the source code, you can do it) and modify all the existing aspx.cs classes of the authenticated pages to extendSSOLib.PrivatePage as suggested. That is:

class PrivatePage : BasePage 
{
    ...
}

If there is any conflicting code or method between the existing BasePage class and the SSOLib.PrivatePageclass, you might need to modify some code in these two classes. It would be preferable not to change the code of SSOLib.PrivatePage unless any bug is discovered, and it would be better to change the existing BasePagecode as required. But, feel free to change the code of SSOLib.PrivatePage if you really need to, it’s all yours!

Hmm.. so who does all the SSO things? Where is the magic?

Good question. In the ideal case, using this example SSO model, you won’t have to write a single line of Sign On oriented code to implement SSO in your ASP.NET applications (except some configuration and inheritance changes). How is this possible? Who is managing all the dirty SSO stuff?

SSOLib and the SSO site are the two magicians doing all the tricks. SSOLib is a DLL which is used by each client site to carry out the following things:

  • Communicate to SSO site via a Web Service.
  • Redirect to the SSO site or login page or serve the requested page.

The following diagram depicts the role of SSOLib in the SSO model:

CrossDomainSSOExample/SSOLib.png

The most important thing inside SSOLib is the PrivatePage class which is inherited by the code-behind pages of the authenticated classes. This class inherits the System.Web.UI.Page class, and overrides the OnLoad()method, as follows:

public class PrivatePage : Page
{
      protected override void OnLoad(EventArgs e)
      {
           //Set caching preferences
           SetCachingPreferences();
                      
           //Read QueryString parameter values
           LoadParameters();

           if (IsPostBack)
           {
               //If this is a postback, do not redirect to SSO site.
               //Rather, hit a web method to the SSO site 
               //to know user's logged in status
               //and proceed based on the status
               HandlePostbackRequest();
               base.OnLoad(e);
               return;
           }

           //If the current request is marked not 
           //to be redirected to SSO site, do not proceed
           if (SessionAPI.RequestRedirectFlag == false)
           {
               SessionAPI.ClearRedirectFlag();
               base.OnLoad(e);
               return;
           }

           if (string.IsNullOrEmpty(RequestId))
           {
               //Absence of Request Paramter "RequestId" means 
               //current request is not redirected from SSO site.
               //So, redirect to SSO site with ReturnUrl
               RedirectToSSOSite();
               return;
           }
           else
           {
               //Current request is redirected from 
               //the SSO site. So, check user status
               //And redirect to appropriate page
               ValidateUserStatusAndRedirect();
           }

           base.OnLoad(e);
       }
}

Basically, OnLoad() is called whenever a Page object is loaded as a result of a URL hit in the browser, and the core SSO logic is implemented inside this method. All the codes is self descriptive and documented to depict what is going on.

More on the SSOLib functionality in the following sections.

What does http://www.sso.com do?

The SSO site has the following two important functionalities:

    • User authentication and user retrieval Web Services which are accessed by the client sites via the SSOLib DLL to authenticate the user and to know the user’s logged-in status on the SSO site. The following services are available:

CrossDomainSSOExample/SSOWebServices.png

    • Setting and retrieving user authentication cookie using an ASPX page (Authenticate.aspx) which is redirected by SSOLib for setting / checking or removing the cookie based upon the type of request.

Following is the core functionality that is performed by Authenticate.aspx. The codes is self-descriptive and documented for easy understandability.

protected void Page_Load(object sender, EventArgs e)
{
   //Read request paramters and populate variables
   LoadRequestParams();

   if (Utility.StringEquals(Action, AppConstants.ParamValues.LOGOUT))
   {
       //A Request paramter value Logout indicates
       //this is a request to log out the current user
       LogoutUser();
       return;
   }
   else
   {
       if (Token != null)
       {
           //Token is present in URL request. That means, 
           //user is authenticated at the client site 
           //using the Login screen and it redirected
           //to the SSO site with the Token in the URL parameter, 
           //so set the Authentication Cookie
           SetAuthCookie();
       }
       else
       {
           //User Token is not available in URL. So, check 
           //whether the authentication Cookie is available in the Request
           HttpCookie AuthCookie = 
              Request.Cookies[AppConstants.Cookie.AUTH_COOKIE];
           if (AuthCookie != null)
           {
               //Authentication Cookie is available 
               //in Request. So, check whether it is expired or not.
               //and redirect to appropriate location based upon the cookie status
               CheckCookie(AuthCookie, ReturnUrl);
           }
           else
           {
               //Authentication Cookie is not available 
               //in the Request. That means, user is logged out of the system
               //So, mark user as being logged out
               MarkUserLoggedOut();
           }
       }
   }
}

Looks pretty good. Did you have to handle any critical issues?

Another good question. The core SSO logic seems pretty straightforward. That is:

If current request is a PostBack, 
    If this is a PostBack in Login page (For Login)
        Do Nothing
    Else
        Do not redirect to SSO site. Rather, invoke a web service at SSO site 
        to know user's logged in status, using the User *Token. 
    If user is not logged out 
        Proceed the normal PostBack operation.
    Else 
        Redirect to login page
Else
    If current request is not redirected from the SSO Site, 
        Redirect it to SSO site with   setting ReturnUrl with 
        the current Request URL and parameters.
    Else
        Get user's Logged in status on SSO Site 
        by invoking a web service with user *Token

        If user is logged out there, 
            Redirect to Login page
        If current request is a page refresh, 
            Redirect to SSO site with ReturnUrl
        Else 
            Redirect to the originally requested URL
    End If
End If

*User token is a hash code of a GUID that identifies a user’s login onto the SSO site uniquely. Each time a user is logged onto the SSO site, the token is generated at the SSO site, and this token is used later to set the authentication cookie and to retrieve the user object by the client sites.

But there are some obvious issues that were needed to be handled to implement the SSO logic. These are marked in bold in the above logic:

Implement “Redirect to login page” and “Redirect to the originally requested URL

SSOLib.PrivatePage redirects to the SSO site, or redirects to the currently requested page at the client site, based upon the situation. But, there is a problem if SSOLib.PrivatePage redirects to a page of the current site. As each authenticated page extends the SSOLib.PrivatePage class, a redirect to a page in the current site from SSOLib.PrivatePage would redirect to itself again and again, and will cause an infinite redirect loop.

To solve this issue, an easy fix could be to add a request parameter (say, Redirect=false) to indicate that the request should not be redirected any further. But, this would allow the user to see the Request parameter and allow the user to “hack” the system by altering its value. So, instead of using a Request parameter, I used a Session variable to stop further redirection, before redirecting to any URL of the current site fromSSOLib.PrivatePage. In OnLoad(), I check the Session variable and reset it and return as follows:

//If the current request is marked not to be redirected to SSO site, do not proceed
if (SessionAPI.RequestRedirectFlag == false)
{
   SessionAPI.ClearRedirectFlag();
   return;
}

Detect whether “Current request is not redirected from the SSO Site“, and whether “current request is a page refresh

SSOLib.PrivatePage redirects to the SSO site for setting or checking the authentication cookie. After the SSO site is done with its work, it redirects back to the calling site using the URL that is set in ReturnUrl.

This also creates a scenario where the client site might again redirect to the SSO site and the SSO site again redirects to client site and creates an infinite redirection loop. Unlike the previous situation, this time, a Session variable could not be used because the redirection is occurring from the SSO site, and the client site and the SSO site have different Session states. So, a Request parameter value should be used to prevent further redirect to the SSO site once the SSO site redirects to the client site.

But again, using a Request parameter to prevent redirection would allow the user to alter it and break the normal functionality. To work-around this, the Request parameter value is set with a hash of a GUID(RequestId=Hash(New GUID)), and this is appended from the SSO site before redirecting back to the client-site URL.

The redirect request executes the OnLoad() method of SSOLib.PrivatePage again, and this time, it finds theRequestId, and this indicates that this request is redirected back from the SSO site and hence this should not be redirected to the SSO site further.

But, what if the user alters the value of the RequestId in the query string and hits the URL, or the user just refreshes the current page?

As each different request is to be redirected to the SSO site (except the postback hits), in this case, this request should be redirected to the SSO site as usual. But, the request URL already contains a RequestId, and despite this, the request should be redirected to the SSO site. So, how should SSOLib.PrivatePage understand this?

There is only one way. A specific RequestId should be valid for each particular redirect from the SSO site only, and once the RequestId is received at the client site from the Request parameter, it should expire instantly so that even if the next URL hit contains the same RequstId, or if the next URL contains an invalid value, it redirects to the SSO site.

The following logic has been used to handle this scenario:

if (string.IsNullOrEmpty(RequestId))
{
   //Absence of Request Paramter RequestId means current 
   //request is not redirected from SSO site.
   //So, redirect to SSO site with ReturnUrl
   RedirectToSSOSite();
   return;
}
else
{
   //Current request is redirected from the SSO site. So, check user status
   //And redirect to appropriate page
   ValidateUserStatusAndRedirect();
}

//And,

UserStatus userStatus = AuthUtil.Instance.GetUserStauts(Token, RequestId);
if (!userStatus.UserLoggedIn)
{
   //User is not logged in at SSO site. So, return the Login page to user
   RedirectToLoginPage();
   return;
}
if (!userStatus.RequestIdValid)
{
   //Current RequestId is not valid. That means, 
   //this is a page refresh and hence, redirect to SSO site
   RedirectToSSOSite();
   return;
}
if (CurrentUser == null || CurrentUser.Token != Token)
{
   //Retrieve the user if the user is not found 
   //in session, or, the current user in session
   //is not the one who is currently logged onto the SSO site
   CurrentUser = AuthUtil.Instance.GetUserByToken(Token);
   if (CurrentUser.Token != Token || CurrentUser == null)
   {
       RedirectToSSOSite();
       return;
   }
}

On the other hand, before redirecting to the client site, the SSO site generates a RequestId, appends it with the query string, and puts it in Application using the RequestId as the key and value. Following is how the SSO site redirects back to the client site:

/// <summary>
/// Append a request ID to the URl and redirect
/// </summary>
/// <param name="Url"></param>
private void Redirect(string Url)
{
    //Generate a new RequestId and append to the Response URL.
    //This is requred so that, the client site can always
    //determine whether the RequestId is originated from the SSO site or not
    string RequestId = Utility.GetGuidHash();
    string redirectUrl = Utility.GetAppendedQueryString(Url, 
              AppConstants.UrlParams.REQUEST_ID, RequestId);
    
    //Save the RequestId in the Application
    Application[RequestId] = RequestId;

    Response.Redirect(redirectUrl);
}

Note that, before redirection, RequestId is stored in the Application scope to mark that this RequestId is valid for this particular response to the client site. Once the client site receives the redirected request, it executes the GetUserStatus() Web Service method, and following is how the GetUserStatus() web method clears the RequestId from the Application scope so that any subsequent requests with the same RequestId or any request with an invalid RequestId can be tracked as an invalid RequestId:

/// <summary>
/// Determines whether the current request is valid or not
/// </summary>
/// <param name="RedirectId"></param>
/// <returns></returns>
[WebMethod]
public UserStatus GetUserStauts(string Token, string RequestId)
{
      UserStatus userStatus = new UserStatus();

      if (!string.IsNullOrEmpty(RequestId))
      {
          if ((string)Application[RequestId] == RequestId)

          {
              Application[RequestId] = null;
              userStatus.RequestIdValid = true;
          }
      }

      userStatus.UserLoggedIn = 
        HttpContext.Current.Application[Token] == null ? false : true;

      return userStatus;
}

Get user’s logged in status on the SSO site

The GetUserStauts() Web Service method returns the user’s status inside a UserStatus object, which has two properties: UserLoggedIn and RequestIdValid.

Once a user is logged onto the SSO site via the Authenticate Web Service method, it generates a User Token (hash code of a new GUID) and stores the user Token inside an Application variable using the Token as the Key:

/// <summary>
/// Authenticates user by UserName and Password
/// </summary>
/// <param name="UserName"></param>
/// <param name="Password"></param>
/// <returns></returns>
[WebMethod]
public WebUser Authenticate(string UserName, string Password)
{
   WebUser user = UserManager.AuthenticateUser(UserName, Password);
   if (user != null)
   {
       //Store the user object in the Application scope, 
       //to mark the user as logged onto the SSO site
       //Along with the cookie, this is a supportive way 
       //to trak user's logged in status
       //In order to track a user as logged onto the SSO site 
       //user token has to be presented in the cookie as well as
       //he/she has to be presented in teh Application scope
       HttpContext.Current.Application[user.Token] = user;
   }
   return user;
}

When the user logs out of the system from any client site, the authentication cookie is removed, and also the user object is removed from the Application scope (inside Authenticate.aspx.cs in the SSO site):

/// <summary>
/// Logs out current user;
/// </summary>
private void LogoutUser()
{
   //This is a logout request. So, remove the authentication Cookie from the response
   if (Token != null)
   {
       HttpCookie Cookie = Request.Cookies[AppConstants.Cookie.AUTH_COOKIE];

       if (Cookie.Value == Token)
       {
           RemoveCookie(Cookie);
       }
   }
   //Also, mark the user at the application scope as null
   Application[Token] = null;

   //Redirect user to the desired location
   //ReturnUrl = GetAppendedQueryString(ReturnUrl, 
   //   AppConstants.UrlParams.ACTION, AppConstants.ParamValues.LOGOUT);
   Redirect(ReturnUrl);
}

So, without redirecting to the SSO site, it is possible to know the user’s logged in status just by checking the user’s presence in the Application scope of the SSO site. The client sites invoke the Web Service method of the SSO site, and the SSO site returns the user’s logged in status inside the UserStatus object.

This method of knowing the user’s logged in status is handy because when a postback occurs, the client sites would not want to redirect to the SSO site (because, if they do that, the postback event methods cannot be executed).

In such cases, they invoke the web method to know the user’s logged in status, and if the user is not available at the SSO site, the current request is redirected to the login page. Otherwise, the normal postback event method is executed.

Wait a minute, storing the user in the Application scope should mark the user logged in for all sessions. How do you handle that?

True. Once a user is authenticated, he/she is stored in the Application scope to mark as logged in. But, theApplication scope is a global scope irrespective of the site and user sessions. So, there is a risk that the user might also get marked as logged in for all browser sessions.

This sounds risky. But, this is handled with care so that the user object of a particular browser session is not available to other browser sessions. Let us now see how this has been handled.

Once a user logs onto the SSO site, the user is stored in the Applicationscope against the user Token, which is valid only for a particular user Login session.

If some direct request is hit in a new window (hence with a new Session) with the user Token (with or without the RequestId) by copying the URL from the address bar, the system will not let the URL request bypass the login screen. Why? Because the authentication cookie that is set by the SSO site is a “non-persistent” cookie, and hence this cookie is sent by the browser to the SSO site only if subsequent requests are hit in the same browser session (from the same browser window or different tabs in the same window). That means, if a new browser window is opened, it does not have any authentication cookie to send to the SSO site, and naturally, the request is redirected to the login page of the client site. So, even if a user is stored in the Application scope in the SSO site, that user object is stored against a different user Token as a key, that can never be accessed for any new request in the new session, because this request does not know about the existing user Token, and once the user logs onto this new browser session, it gets a new user Token which never matches with the existing ones.

How cookie timeout and sliding expiration is maintained?

The web.config of the SSO site has configuration options for configuring the cookie timeout value and for enabling/disabling the sliding expiration of the cookie.

<appSettings>
    <add key="AUTH_COOKIE_TIMEOUT_IN_MINUTES" value="30"/>
    <add key="SLIDING_EXPIRATION" value="true"/>
</appSettings>

The cookie timeout value can be configured in the web.config of the SSO site and the timeout value applies to all client sites under the SSO. That is, if the cookie timeout value is specified in the web.config as 30 minutes and ifuser1 logs onto http://www.domain1.com, the cookie is available for the next 30 minutes in the browser, and henceuser1 is signed on the other two sites for this 30 minutes, unless user1 is logged out of the site.

Now, how is this cookie timeout implemented? Simple, by setting the cookie expiration time, of course.

Unfortunately, I couldn’t do that. Why? Because, by default, when a cookie is set in the Response, it is created as a non-persistent cookie (the cookie is stored only in the browser’s memory for the current session, not in the client’s disk). If the expiry date is specified for the cookie, ASP.NET runtime automatically instructs the browser to store the cookie as a persistent cookie.

In our case, we don’t want to create a persistent cookie, because this will let the other sessions to also send the authentication cookie to the SSO site and eventually mark the user as logged in. We do not want that to happen.

But, the expiration datetime has to be set somehow. So, I stored the expiration value in the cookie’s value, along with appending to the user’s Token, as follows:

/// <summary>
/// Set authentication cookie in Response
/// </summary>
private void SetAuthCookie()
{
   HttpCookie AuthCookie = new HttpCookie(AppConstants.Cookie.AUTH_COOKIE);

   //Set the Cookie's value with Expiry time and Token
   int CookieTimeoutInMinutes = Config.AUTH_COOKIE_TIMEOUT_IN_MINUTES;

   AuthCookie.Value = Utility.BuildCookueValue(Token, CookieTimeoutInMinutes);
   //Appens the Token and expiration DateTime to build cookie value

   Response.Cookies.Add(AuthCookie);

   //Redirect to the original site request
   ReturnUrl = Utility.GetAppendedQueryString(ReturnUrl, 
                        AppConstants.UrlParams.TOKEN, Token);
   Redirect(ReturnUrl);
}

/// <summary>
/// Set cookie value using the token and the expiry date
/// </summary>
/// <param name="Value"></param>
/// <param name="Minutes"></param>
/// <returns></returns>
public static string BuildCookueValue(string Value, int Minutes)
{
    return string.Format("{0}|{1}", Value, 
       DateTime.Now.AddMinutes(Minutes).ToString());
}

Eventually, when the cookie is received at the SSO site, its value is retrieved as follows:

/// <summary>
/// Reads cookie value from the cookie
/// </summary>
/// <param name="cookie"></param>
/// <returns></returns>
public static string GetCookieValue(HttpCookie Cookie)
{
   if (string.IsNullOrEmpty(Cookie.Value))
   {
       return Cookie.Value;
   }
   return Cookie.Value.Substring(0, Cookie.Value.IndexOf("|"));
}

And, the expiration date time is retrieved as follows:

/// <summary>
/// Get cookie expiry date that was set in the cookie value
/// </summary>
/// <param name="cookie"></param>
/// <returns></returns>
public static DateTime GetExpirationDate(HttpCookie Cookie)
{
   if (string.IsNullOrEmpty(Cookie.Value))
   {
       return DateTime.MinValue;
   }
   string strDateTime = 
     Cookie.Value.Substring(Cookie.Value.IndexOf("|") + 1);
   return Convert.ToDateTime(strDateTime);
}

If SLIDING_EXPIRATION is set to true in the web.config, the cookie expiration date-time value is increased with each request, with the minute value specified in AUTH_COOKIE_TIMEOUT_IN_MINUTES in the web.config. The following code does that:

/// <summary>
/// Increases Cookie expiry time
/// </summary>
/// <param name="AuthCookie"></param>
/// <returns></returns>
private HttpCookie IncreaseCookieExpiryTime(HttpCookie AuthCookie)
{
   string Token = Utility.GetCookieValue(AuthCookie);
   DateTime Expirytime = Utility.GetExpirationDate(AuthCookie);
   DateTime IncreasedExpirytime = 
     Expirytime.AddMinutes(Config.AUTH_COOKIE_TIMEOUT_IN_MINUTES);

   Response.Cookies.Remove(AuthCookie.Name);

   HttpCookie NewCookie = new HttpCookie(AuthCookie.Name);
   NewCookie.Value = 
     Utility.BuildCookueValue(Token, Config.AUTH_COOKIE_TIMEOUT_IN_MINUTES);

   Response.Cookies.Add(NewCookie);

   return NewCookie;
}

Can this implementation be used for production systems?

Yes! It surely can be used, but before that, some security and other cross-cutting issues have to be addressed. This is just a basic implementation, and I didn’t verify the model with a professional Quality Assurance process (though I did some basic acceptance testing myself). Also, this authentication does not offer the full flexibility and powers that Forms authentication provides. Additionally, it does not have the built-in authorization mechanism of Forms authentication, and hence you might need to write some more customization on the current SSO implementation, based upon your specific requirements.

However, I’ll try to update the SSO model to enrich it with more features and make it robust so that this could be used in commercial systems without requiring any customization.

Any suggestion or feedback is highly welcome. Adios!

REF:

http://www.codeproject.com/Articles/106439/Single-Sign-On-SSO-for-cross-domain-ASP-NET-applic

http://www.codeproject.com/Articles/114484/Single-Sign-On-SSO-for-cross-domain-ASP-NET-appl

Advertisements

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