Sunday, May 15, 2011

ASP.NET Forms Authentication Sharing for Silverlight/WCF applications

Over two years ago I’ve blogged on how to share the forms authentication between ASP.NET and ClickOnce applications. We use this technique often for it’s just convenient to have just one, centralized authentication mechanism (ASP.NET Membership Provider in this case) in large modular systems.
In just few words – that sharing is possible by passing a Forms authentication cookie to the ClickOnce application as an Uri parameter and append the cookie to the CookieContainer of a webservice proxy class. This way all requests from the ClickOnce application back to the server (which pass through the FormsAuthenticationModule) are correctly recognized as authenticated and in the same time an exception is thrown in case of a missing or invalid authentication cookie. On the server side – web service methods are guarded with the PrincipalPermission attribute. Please, refer to the article for more details.

As we finally add Silverlight to our daily toolbox, I strongly needed a corresponding mechanism for WCF Services and Silverlight proxies. I need to share the Forms authentication between my ASP.NET hosting application and the Silverlight application users run after they login to the ASP.NET application.
What seemed to be a piece of cake, has turned out to be tricky, mostly because of the way in which exceptions are passed between the server and the Silverlight at the client side.

Guarding WCF calls with PrincipalPermission – an easy step
The first step is easy. Since Silverlight is hosted in web browser, there’s no need to copy cookies as cookies are appended automatically to WCF service calls (assuming that both hosting application and hosted Silverlight come from the same domain but this is usually the case). Assuming then that users need to login first using Forms authentication and then they get access to the Silverlight application, whenever the Silverlight calls back the WCF on the server, Forms authentication cookies are there.
So I just happily put PrincipalPermission over my WCF methods but unfortunately no matter whether I logged first or not, I got an exception all the time. It seems that WCF needs an few additional spells to be casted for this to work (and these spells was not needed for ASP.NET WebServices):
an additional AspNetCompatibilityRequirements attribute over the WCF class
setting Thread.CurrentPrincipal in the WCF constructor

[ServiceContract( Namespace = "http://my.namespace.com/service1" )]
[AspNetCompatibilityRequirements( RequirementsMode = AspNetCompatibilityRequirementsMode.Required )]
public class Service1
{
    public Service1()
    {
        /* this line is crucial for PrincipalPermission to work */
        Thread.CurrentPrincipal = HttpContext.Current.User;
    }
    ... WCF methods follow ...
forcing the runtime to use ASP.NET compatibility mode for WCF (web.config)
...
<system.serviceModel>        
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    ...
</system.serviceModel>