XAML Playground
about XAML and other Amenities

FormsAuthentication and ClientHttpStack with mixed applications

2010-08-30T16:41:47+01:00 by Andrea Boschin

If you ever tried to have mixed applications, with some regular ASP.NET pages and a bunch of Silverlight applications, where the forms authentication is made with a traditional ASP.NET login page, you have for sure found that you are forced to use the BrowserHttp stack into the Silverlight applications because the FormsAuthentication cookie is not shared between the ASP.NET page and the new ClientHttp stack that is available since Silverlight 3.0.

Since the new ClientHttp stack for many reasons is a huge improvement, is would be a beautiful thing being able to use this stack every time there is the need to place HTTP calls. Finally I found a way to share this cookie across the two stacks, but I want to warn you that the sole way to accomplish this task require a little "dirty hack", that someone might consider unacceptable while it opens some minor security concerns I will detail at the end of the post.

Before continue reading this post please have in mind that my suggestion is to always use the BrowserHttp stack in these scenario, until you do not really need the new stack, e.g. if you have to use some special verb or for some other particular reason.

The dirty hack explained

The first thing you have to know is that when you are using the ClientHttp stack you are free to access the cookies issued by the server and it is also possible to add some cookies to attach to a call. The task we would have to accomplish is simply to read a cookie (often called .ASPXAUTH) from the BrowserHttp stack and add it to the ClientHttp stack and it may seems and easy task.

Unfortunately the BrowserHttp Stack prevent us from accessing the cookies. The cookies management is one of the features that has been added by the new ClientHttp stack and there is not any way to pump out them from the old one.

So my first try was to use the HtmlPage.Document.Cookies property, as I found in some examples on the Internet. Perhaps this may work when the authentication cookie is issued by a custom authentication system, but with FormsAuthentication it is another failed try because the cookies issued by the ASP.NET engine are HttpOnly. This attribute, added since IE6 SP1, prevents the browser scripts - and Silverlight is considered a client script - from accessing these cookies that remain hidden.

So, how can we share these cookies? The only thing you can do is to add the cookie to the InitParams collection when you initialize the plugin in the html page. You have to change this page to be an aspx page and then you can create two keys into the InitParams collection like the following:

   1: public string GetFormsAuthenticationCookie()
   2: {
   3:     return string.Format(
   4:         "FormsCookieName={0},FormsCookieValue={1}",
   5:         FormsAuthentication.FormsCookieName,
   6:         this.Request.Cookies[FormsAuthentication.FormsCookieName].Value);
   7: }

Then in the plugin I would like to have a centralized class reponsible of creating the cookie and adding it to each server call I do. First of all, if you are using WCF you have to enable the CookieContainer from the ServiceReferences.ClientConfig file. You have to add an attribute that exists only in the Siverlight side of the configuration:

   1: <basicHttpBinding>
   2:     <binding name="BasicHttpBinding_IWCFService" 
   3:               maxBufferSize="2147483647"
   4:               enableHttpCookieContainer="True"
   5:               maxReceivedMessageSize="2147483647">
   6:         <security mode="None" />
   7:     </binding>
   8: </basicHttpBinding>

The enableCookieContainer attribute initializes an instance of an IHttpCookieContainerManager into the WCF client an this allow us to manage cookies using the CookieContainer collection. Now we can write a simple application service to put into the app.xaml that we can use to instantiate and initialize the client. The reason of creating an application service is to have a common place where initialize a shared cookie container that reads the InitParam and add the received cookies into it:

   1: public class ClientCreatorService : IApplicationService
   2: {
   3:     public static ClientCreatorService Current
   4:     {
   5:         get
   6:         {
   7:             return Application.Current.ApplicationLifetimeObjects.OfType<ClientCreatorService>().Single();
   8:         }
   9:     }
  10:  
  11:     private CookieContainer SharedCookies { get; set; }
  12:  
  13:     public void StartService(ApplicationServiceContext context)
  14:     {
  15:         this.SharedCookies = new CookieContainer();
  16:         string formsCookieName = context.ApplicationInitParams["FormsCookieName"];
  17:         string formsCookieValue = context.ApplicationInitParams["FormsCookieValue"];
  18:         this.SharedCookies.Add(new Uri(Application.Current.Host.Source, "../"), new Cookie(formsCookieName, formsCookieValue));
  19:     }
  20:  
  21:     public K CreateClient<T, K>()
  22:         where K : ClientBase<T>, new()
  23:         where T : class
  24:     {
  25:         K client = new K();
  26:         IHttpCookieContainerManager cookieManager = client.InnerChannel.GetProperty<IHttpCookieContainerManager>();
  27:         cookieManager.CookieContainer = this.SharedCookies;
  28:         return client;
  29:     }
  30:  
  31:     public void StopService()
  32:     {}
  33: }

During the initialization the ClientCreatorService initialize the SharedCookies with the FormsCookieName and FormsCookieValue. Then the generic CreateClient methid is able to instantiate the required client and add the SharedCookies to it. The usage of the Application Service is pretty simple. Here is how to instantiate the client and place a call:

   1: WCFServiceClient client = ClientCreatorService.Current.CreateClient<IWCFService, WCFServiceClient>();
   2: client.GetIdentityCompleted += new EventHandler<GetIdentityCompletedEventArgs>(client_GetIdentityCompleted);
   3: client.GetIdentityAsync();

If you try the code you will find that the GetIdentity method is able to retrive the membership identity from the HttpContext.Current.User property. This means your service runs in an authenticated context.

What is the drawback?

When you make a dirty thing you have to be aware that there is always almost a drawback. In this case the problem is that the injection of the authentication cookie in the InitParams opens a subtle security concern that is mitigated by the fact that the informations we are disclosing are already public even before my trick was up and running. The use of HttpOnly cookies for authentication is an important improvement that prevent the use of XSS attacks. Having the cookie injected in the page, re-open the window to some pre-IE6 SP1 scripts that could try to access the InitParams collection from javascript and simulate the authentication to the system like our Silverlight application actually does.

This problem must be always in your mind and you have to put on the scale both the advantage of having the ClientHttp working and the security concerns of this trick. My hope is that the Silverlight team will be able to solve this issue in next releases of the plugin, so we can rely on a secure implementation and avoid dirty tricks.

Categories:   Networking | TIPS
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed