Monday, 17 March 2008

Silverlight, WCF and ASP.NET Authenticated Sites

Silverlight, WCF and ASP.NET Authenticated Sites

Introduction

In order to show integration of a service which we will expose to our own internal Silverlight applications (not third party developers), we will create a time service which will return the server time in UTC. The time service will be hosted within an ASP.NET 3.5 Website.

The service that we create will only be available to logged in users of our website and will leverage the existings forms authentication system. This will mean that our silverlight application will not have to implement it’s own login system but hook into the existing authentication system.

Creating the time service website
In order to create the time service i have performed the following steps:

1) Created a new ASP.NET Website within Visual Studio
2) Created a default.aspx page which has a link to the members area and to the service




<div>
Welcome to my home page, <a href="Members/Default.aspx">click here to go to my members section</a>
</div>



3) Created a login.aspx page (which uses the inbuilt asp.net login control)




<div>
<asp:Login ID="Login1" runat="server"></asp:Login>
</div>




4) Implemented authentication in Web.Config


<authentication mode="Forms">
<forms slidingExpiration="true" timeout="15" loginUrl="~/login.aspx" defaultUrl="~/Members/Default.aspx"></forms>
</authentication>


5) Implemented MyMembershipProvider
This is a simple custom membership provider class which accepts bob as a username and doesn’t care what the password is.

6) Implemented membership in Web.Config


<membership defaultProvider="MyMembershipProvider">
<providers>
<remove name="Default"/>
<add name="MyMembershipProvider" applicationName="MyMembershipProvider" passwordFormat="Encrypted" type="MyMembershipProvider"/>
</providers>
</membership>


7) Restrict access to members and internal services folders


<location path="Members">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>
<location path="InternalServices">
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</location>>


8) Implemented Members default page (Members/Default.aspx)



<div>
<a href="SilverlightTimeAppTestPage.aspx">Click here to go to the time service</a>
</div>



We now have a standard host website which will allow users called Bob to login to the Members area, and restrict non authenticated users from this area of the site.

Implementing the Time Service

So we now wish to create a time service which will reside in the services folder (and will be only available to authenticated users).

1) Right click on the Services Folder -> Add New Item -> WCF Service (and name it TimeService.svc)
2) Define ItimeService.cs (should be in your app_code directory) as



[ServiceContract]
public interface ITimeService
{
[OperationContract]
DateTime GetServerTimeUtc();
}


3) Implement the TimeService.cs as



[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class TimeService : ITimeService
{
/// <summary>
/// Get the server time in utc
/// </summary>
public DateTime GetServerTimeUtc()
{
return DateTime.Now.ToUniversalTime();
}
}


Please note we are forcing the service to require to run under asp.net compatibilit mode. This allows us to utilise the asp.net forms authentication system
4) Modify the System.Service Model in Web.Config
The key thing to modify is to change from wsHttpBinding to basicHttpBinding, and to ensure aspnetcompatibility node is enabled



<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="TimeServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="TimeServiceBehavior" name="TimeService">
<endpoint address="" binding="basicHttpBinding" contract="ITimeService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>



Implementing the Client


1) Ensure the location section of the web.config is commented out
2) Create a new Silverlight application
3) Add Service Reference, click Discover and point to the service
4) Call the proxy something sensible such as TimeServiceProxy
5) Right Click on the Members area of the Website, right click, add silverlight link, to include the test page on the website
6) Uncomment the location tags in the web.config
7) Call the time service off a button click, asynchronously updating a textbox

Call the time service off a button click, asynchronously updating a textbox



private void btnTime_Click(object sender, RoutedEventArgs e)
{
TimeServiceProxy.TimeServiceClient proxy = new SilverlightTimeApp.TimeServiceProxy.TimeServiceClient();
proxy.GetServerTimeUtcCompleted += new EventHandler<SilverlightTimeApp.TimeServiceProxy.GetServerTimeUtcCompletedEventArgs>(proxy_GetServerTimeUtcCompleted);
proxy.GetServerTimeUtcAsync();
}

void proxy_GetServerTimeUtcCompleted(object sender, SilverlightTimeApp.TimeServiceProxy.GetServerTimeUtcCompletedEventArgs e)
{
txtTime.Text = e.Result.ToString();
}


You now have a WCF service that you can use internally with your own silverlight applications, making use of asp.net authentication mechanism, and is protected so only logged in users can use the service from your silverlight applications.

As a security note do not expose this service to third party developers (see my previous post).

The full sample application can be downloaded here from my skydrive

6 comments:

Patrice said...

Good post Chris, just curious on what will happen if the session timeout while calling the service from Silverlight? I suppose you'll receive the login.aspx content (html code)?

Anonymous said...

You know .. it would really help if half the code lines were not completely cut off beyond a certain width.

Anonymous said...

I think e.Error will return an System.ServiceModel.ProtocolException, with a message - "The remote server returned an unexpected response: (404) Not Found.".

anyeone said...

I've been searching for useful Silverlight / WCF security info for awhile and I think this was the most useful post I've come across so far. I'm working on an internet application with very tight security requirements and it seems most people writing Silverlight tutorials are thinking about public "fun" apps rather than apps where data in both directions needs to be tightly controlled. Keeping folks out of our WCF services is critical and information on how to do that is few and far between.

Thanks for posting this.

chrishayuk said...

glad it was useful

Anonymous said...

Thank you for the realy good post. It was realy helpful to me:)