This article is compatible with the latest version of Silverlight.
Introduction
In Part 2 of this series I completed implementing the inventory list screen. The focus of this article is to describe the process of transferring data between the server (a WCF service) and the client (the Silverlight application), securely and sharing common business logic where possible.
Source Code* and Live Demo**
*To run the source code take a look at the "Using the sample application" section of the previous article first.
**To login in the sample application use the following Username: demo and Password: demo.
Be sure to check all articles of the series: Part 1, Part 2, Part 3, Part 4, Part 5 and Part 6
Securing Your WCF Service
It is important that any data you make available via the internet is accessible by only those with permission to view that data, and they can only make the modifications or perform the actions they are permitted based upon their role. When you make data available via the internet you need to view it as if you were a hacker (cracker is probably the proper term, but mildly savvy users can also exploit a number of weaknesses) and attempt to identify any ways that someone may attempt to work around your security measures. It is then your task to ensure these holes are plugged.
This is a Line-Of-Business application, therefore by definition you are dealing with critical and sensitive business data that in the wrong hands could potentially bring a business to its knees. The larger and more public a company is the higher the risk someone will attempt to find a hole and circumvent the system, but small companies should not be lax on this point either despite their reduced target size. Angry ex-employees are often the culprits in system break-ins and can potentially wreak havoc. Corporate espionage from rivals is not unknown either. So it is vital that you secure access to your business critical data.
In Part 1 I touched on transferring data between the server and the client, with a method to validate user credentials (Login) and retrieving a list of products (GetProductList). User credentials were validated against the built in SQL Server membership providers. However you may have noted that anyone regardless of whether they had previously logged in who wanted to “hack” the system could directly call the GetProductList function and still retrieve a product list as this function didn’t check whether this user had previously logged in and had the permission to call this method. This is a security hole that we need to plug by ensuring that with every service call the user has permission to perform that action.
There are numerous means of securing access to your WCF service (only permitting access to logged in users or users assigned to a particular role), each with a number of pros and cons. I will detail two of these methods that make it to the top of my list – the first using forms authentication cookies, and the second using message headers.
Security Using the ASP.NET Forms Authentication Cookie
If you’ve worked with ASP.NET previously you’ve no doubt used the built in forms authentication functionality. Essentially once the user has been authenticated (using the SQL Server membership provider or a custom provider that you implement yourself) they are issued with a cookie that they send to the server with each additional page request, containing their authentication information. This cookie holds a forms authentication ticket containing the user name, the issue date, the expiry date, and miscellaneous other information, all encrypted (with the machine key) so the details can’t be read without knowing the key to decrypt it. This cookie and the ticket it contains exhibit the following behaviours:
- The cookie (and separately the ticket inside it) expires after a set period of time (by default 30 minutes). This means that if someone was sniffing your network traffic and attempted to “spoof” your cookie it would quickly become invalid as the expiry date contained within the ticket would soon be reached and access would thus be denied.
- So that the user would not have to log in every 30 minutes whilst they are actively interacting with the server (or whatever the timeout period is set to) the cookie/ticket implements a sliding expiration scheme where with each request to the server the cookie will be updated with a new ticket that extends the expiry to 30 minutes from that request. Note however this behaviour includes a few caveats which you can explore further in the articles in the resources at the end.
- You can set the cookie to be persistent (across browser sessions). Note that creating a persistent cookie does not mean that the cookie will remain forever – it will still expire at its set expiry date (generally after 30 minutes). I have to admit previously being rather confused by this point as I expected that a persistent cookie would last forever, but found that the meaning of persistent in this case did not equate to being permanent.
- The cookie is created with the HTTPOnly cookie attribute that prevents client side script (and Silverlight!) from reading the cookie for security purposes.
You can override all these behaviours between configuration in the web.config and through code if you need to – but it can make identifying issues difficult when other programmers come to maintain the code, and these are configured this way by default for security purposes so it’s not really recommended to override the behaviour in code.
I describe this at a rather high level because there is actually quite a bit of complexity in how this cookie works and its behaviour – more than what I want to go into here. However I’ve included some resources at the end that you can use to investigate this further.
In Silverlight we can also leverage the ASP.NET forms authentication cookie to provide authentication functionality to our WCF service. By using the following function to send a cookie back to the client when the user successfully logs in:
FormsAuthentication.SetAuthCookie(userName, false);
this cookie will automatically be sent with each additional call to our WCF service (until it times out) and we can use this to identify the user. If we configure our service (in its web.config) to be compatible with ASP.NET by adding the following node under the system.serviceModel section:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
and configuring our membership and role providers in the web.config file (as you would in a standard ASP.NET project) the user will automatically be authenticated and assigned as the current user of the HTTPContext.
The next thing that we need to do is actually accept or deny this user access to each method on our WCF service, based upon whether the caller is authenticated and possibly whether they are assigned to a particular role. An elegant means of doing this is by using declarative security, decorating each method with a PrincipalPermission attribute and the associated access permission.
Declarative security uses the current principal of the thread (a role principal). This is not automatically assigned when using ASP.NET forms authentication, but it is automatically assigned to the current user of the HTTP context so we can assign that value to the current principal of the thread in the constructor of the service like so:
Thread.CurrentPrincipal = HttpContext.Current.User;
We can then apply our attributes to each method to demand that the user is authenticated first:
[PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
Or that the user is assigned to a particular role:
[PrincipalPermission(SecurityAction.Demand, Role="Administrator")]
In the prior version of this application (see the code for Part 2) I used this functionality to authenticate the user, so you can see the above method in action there. However there are some issues when using the forms authentication scheme which turned me away from using it (though I will still continue to use the declarative security). My primary issue against transparently having the cookie containing the authentication ticket passed between the client and the server is that this cookie expires, and there is no way of knowing from Silverlight when it has expired (other than making a call to the service and it failing). Because it is created with the HTTPOnly cookie attribute (as previously described) we can’t determine through Silverlight whether it exists or not, and re-login automatically when it doesn’t. We could attempt to determine when it would have expired by monitoring when the last call was made to the service, but due to the complexity of the sliding expiration scheme it just becomes too unwieldy and complicated to make it worth the effort. We could override the default behaviour of the cookie on the server side (ie. set the expiry of the cookie to sometime long into the future) but I wasn’t particularly fussed with this approach either (though it would work, and you may choose to use this method for this reason).
Security Using SOAP Message Headers
As of RC0 of Silverlight, functionality was added to Silverlight to support SOAP message headers. I decided to investigate this as a means of passing the user credentials to the service as a per call authentication scheme. To implement this we need to create an operation context which has the outgoing message headers to add the credentials object to before making our call. I have created a class to hold the credentials with two properties – UserName and Password. Using this class we create an object containing the user credentials which we pass to the service in the message header. For example this is the code now required to request the product list:
// Add our user credentials to the message headers
using (OperationContextScope scope = new OperationContextScope(service.InnerChannel as IClientChannel))
{
MessageHeader header = MessageHeader.CreateHeader(typeof(UserCredentials).Name,
UserCredentials.WS_NAMESPACE, new UserCredentials(Globals.UserName, Globals.Password), false);
OperationContext.Current.OutgoingMessageHeaders.Clear();
OperationContext.Current.OutgoingMessageHeaders.Add(header);
// Now request data from the service
service.GetProductListCompleted +=
new EventHandler<GetProductListCompletedEventArgs>(AWDataService_GetProductListCompleted);
service.GetProductListAsync(SummaryListToolbar.CurrentPageNumber,
Globals.LIST_ITEMS_PER_PAGE, m_lastFilterString, string.Join(", ", sortBy));
}
Note that we need to initiate the call to the service while within the using block of the OperationContextScope. We would also need to add the code to add the message header every time we wanted to call the service – not particularly elegant (ie. repeated code, extra lines with each call, etc). The next section on instantiating the WCF service from a central location will describe a better method of adding the credentials in the message header for each service call to reduce the above code to this:
// Now request data from the service
AWDataServiceClient service = AWServiceFactory.GetService();
service.GetProductListCompleted +=
new EventHandler<GetProductListCompletedEventArgs>(AWDataService_GetProductListCompleted);
service.GetProductListAsync(SummaryListToolbar.CurrentPageNumber, Globals.LIST_ITEMS_PER_PAGE,
m_lastFilterString, string.Join(", ", sortBy));
which is much more acceptable and elegant, reducing the code required to make a call to the service the same as when using the first method of authentication.
On the server we can now retrieve the user credentials object from the incoming headers, validate the user name and password, create a role principal for that user, and assign it to both the user of the current HTTP context and to the current principal of the current thread. Our declarative security attributes will take care of the rest.
MessageHeaders messageHeaders = OperationContext.Current.IncomingMessageHeaders;
int headerIndex = messageHeaders.FindHeader(typeof(UserCredentials).Name, UserCredentials.WS_NAMESPACE);
if (headerIndex != -1)
{
UserCredentials userCredentials = messageHeaders.GetHeader<UserCredentials>(headerIndex);
AWBusinessObjects.Security.User user = Login(userCredentials.UserName, userCredentials.Password);
if (user != null)
{
// Generate a role principal representing the security context for this user, and
// assign it to the current HTTP context and the current thread for them to run under
RolePrincipal securityPrincipal = new RolePrincipal(new GenericIdentity(userCredentials.UserName));
HttpContext.Current.User = securityPrincipal;
Thread.CurrentPrincipal = securityPrincipal;
}
}
For the purpose of this sample application (based upon the pros and cons I’ve identified) I have decided to use the second authentication method – passing the user credentials in the header of each call to the service. However either of the two methods described above are valid to use as an authentication scheme and you are free to use whichever you feel is appropriate.
Using SSL
Despite the above security measures it is still possible for someone to break into your system. Someone monitoring (aka “sniffing”) the traffic to your server could watch for users logging in and capture their login credentials or authentication ticket/cookie, as they are being sent in plain text across the internet. They could then use these to impersonate that user and obtain access to your system. You could encrypt these details (very easy considering it would be easy to write an encryption/decryption routine in Silverlight) making it harder for someone sniffing your traffic to identify these details. But it’s still not a robust solution as a Silverlight application can easily be decompiled using Reflector (especially easy when combined with the Silverlight Spy tool) so a hacker could easily gain access to your encryption/decryption key and algorithm rendering it useless). Or they could just impersonate you using the encrypted details as easily as if they were decrypted. You can make life harder for hackers but you are unlikely to discourage them from ultimately reaching their goal. To test how easy it is to sniff the data being transferred try using Fiddler or use Silverlight Spy – both can capture and show all network traffic between your machine and the server.
If you didn’t want to pass the user name and password with every service call when using the second forms authentication method, you could instead generate a forms authentication ticket on the server (the same as is stored in the cookie using the first authentication method), return that from the Login method, and pass that with each call to the service in the header instead of the user credentials object. Either could be sniffed and spoofed (and the ticket would still face the expiry problem) so I believe there is little advantage to doing so. The ticket doesn’t contain the password, so even if the user being spoofed changed their password the ticket would remain valid. But if someone managed to sniff the traffic and find the username and password combination then a spoofing attack would not be necessary – the hacker could just log into the application normally using that user’s credentials. Ultimately neither is perfect so the only viable solution in this case is to use SSL for all data communication between the server and the client.
With the release version of Silverlight we are now able to call our WCF service using a https (encrypted) connection, whilst our application is run from a http (non-encrypted) connection. To do this you will first need a valid SSL certificate installed on your server. Note that you cannot use a self signed certificate when calling a WCF service over a https connection from Silverlight – it must be a authentic certificate with trusted roots that you purchase from a certification vendor, otherwise you will receive a security exception when making a call to the service.
I’ve updated the code that determines the url of the WCF service in the framework so that it uses the security mode set in the ServiceReferences.ClientConfig file for the basic http binding. If you set this to ‘None’ then it will use the http scheme, if you set it to ‘Transport’ then you will be using the secure https scheme.
Note that if you decide to secure communication with the WCF service (in a cross scheme implementation) then you will need to place a client access policy file at the root of the domain to permit this cross scheme access (similar to if you were to call a service on a different domain). I have included a sample client access policy file in the AWWeb project in the sample application accompanying this article.
Ultimately I have to leave you to decide which method is the most suitable for you and the extent to which you believe security of the system is of importance. But for a real LOB application implementation I highly recommend secure communication with the WCF service.
Centralised WCF Service Instantiation
As we’ve seen, to support our more secure infrastructure we need to pass the client credentials in the message header with every WCF service call, in addition to specifying the url of the service. To do this for every call would mean a lot of additional and repeated code, so we need a single and centralised means of instantiating the web service and attaching/specifying these details. So essentially what we require is a factory for instantiating our WCF service. This poses a major issue however – as previously discussed I’ve chosen to go with the method of sending user credentials in the message header, and to do so we need to use an operation context with every service call. This is still a lot of extra repeated code – you can’t just set this when instantiating an instance of the service. Usually we’d just write a message inspector to attach these details, but IClientMessageInspector isn’t a part of Silverlight so that omission poses a problem. However we are lucky in that Microsoft has provided us with sample code which implements this functionality which we can add into our project. We can then implement our message inspector that adds the client credentials to the message header and pass this inspector to our service when instantiating it. Problem solved! We can now create our factory that allows us to easily instantiate an instance of our service by this code:
AWDataServiceClient service = AWServiceFactory.GetService();
I made one minor change to the sample code from Microsoft to allow the security binding to be passed in with the constructor of the inspector binding (to support communicating with the WCF service over https) as this couldn’t be set via a public property.
You may have noted that I am requesting data from the web service directly from the code behind the presentation layer, which in my mind isn’t ideal. I’ve been doing this up to this point just for simplicity’s sake, but if you need to make the same web service call from different parts of the application then you need to reproduce the call in each of those parts which tightly couples the presentation with calls to the server. For this instalment I will leave the project as it is, but looking to the future I will move this out into a service layer pattern architecture for a better structure.
Sharing Business Logic Between The Server And Client
On the server we have some classes (we’ll call them entities – though this term is not to be confused with the classes modelling our database tables in the Entity Framework) that we use to pass data between the service and the Silverlight client in. Any validation and other business logic (eg. maximum length of a string, etc) that you do on the client should also be done on the server – the server shouldn’t just trust that the data it receives is valid, it should check it itself. Since these rules would be the same between the client and the server it would be ideal to reuse this logic between both. Assuming you don’t have server specific logic in those classes it would be ideal to reuse these same classes in the Silverlight client project. We could split these entity classes out into their own DLL to reuse between both projects but unfortunately a DLL compiled to run on the full .NET Framework isn’t compatible to be used on the Silverlight platform. What we can do however is create two class library (DLL) projects – one compiled for the full .NET Framework and the other for Silverlight, and link the files from one to the other. We are therefore reusing the same source files between both platforms.
I have added two additional projects to the solution: AWBusinessObjects and AWBusinessObjectsSL (the former being for the full framework and the latter for Silverlight). The AWBusinessObjects project contains the files, and the AWBusinessObjectsSL project links to these files. To link the files you add an existing file to the project the normal way, but instead of pressing the Add button in the dialog click the dropdown arrow next to it and select ‘Add As Link’ instead. Any changes you make to a class in one project will be reflected in the other (considering they are the same file).
In my web application project I will add a reference to the AWBusinessObjects project, and in my Silverlight application I will add a reference to the AWBusinessObjectsSL project.
Usually adding a service reference creates a set of data contract classes matching those used by your service on the server. These contain just the public properties (or the properties marked with the DataMember attribute) and none of the logic or methods from the classes on the server. What we actually want is rather than having these generated classes populated with data from the server (or using them to send data back to the server) instead automatically populating the classes from our “shared entities” DLL saving us having to copy data from one structure to another. This can be achieved by selecting to reuse the types in the referenced assembly when adding the service reference. After selecting the service when adding a service reference, click the Advanced button. Select the “Reuse types in specified referenced assemblies” option and select the “shared” DLL (in this case it will be the AWBusinessObjectsSL DLL). Now the classes from our DLL will be used and we will get the benefit of our shared business logic.
Conclusion
We now have a firm and secure base for transferring data between the server and the client. Business logic can now be shared between the server and the client and these classes in Silverlight are populated automatically with the data from the server. Stay tuned for my next article which will cover building a form to add, edit, and delete products from the inventory.
Resources
Forms Authentication Cookie Behaviours:
http://www.codeproject.com/KB/aspnet/Forms_Auth_Internals.aspx
http://support.microsoft.com/kb/910443
http://www.eggheadcafe.com/tutorials/aspnet/198ce250-59da-4388-89e5-fce33d725aa7/aspnet-cookies-faq.aspx
http://www.hanselman.com/blog/WeirdTimeoutsWithCustomASPNETFormsAuthentication.aspx
http://www.asp.net/LEARN/security/tutorial-03-cs.aspx
Declarative Security:
http://www.code-magazine.com/article.aspx?quickid=0611051&page=4
Silverlight Web Services Examples:
http://code.msdn.microsoft.com/silverlightws/Release/ProjectReleases.aspx?ReleaseId=1660