Silverlight 3: Securing your WCF service with a custom username / password authentication mechanism

There seems to be a lot of confusion on the web about how to secure the backend WCF service of your Silverlight application, with a username and a password. In Silverlight 2, things were a bit tricky. The generated proxies of Visual Studio didn’t have a ClientCredentials property. So to send any custom username / password information to your service, you had to manipulate some headers. Another option was to let the browser do the authentication for you, in combination with asp.net and forms authentication, but that would leave your site open for CSRF attacks. This was the most convenient option in Silverlight 2. You can find a good tutorial about combining ASP.NET’s forms authentication with Silverlight here.
Luckily, in Silverlight 3 we have a third option and this is in my opinion the best way to secure WCF services with a username / password for Silverlight. I’m going to cover this option in this blog post.
The WCF Service
We’re going to use a simple WCF service, because the point of interest is of course the security part. This is the service interface:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Runtime.Serialization;
5: using System.ServiceModel;
6: using System.Text;
7:
8:
9: [ServiceContract]
10: public interface IHelloSayer
11: {
12: [OperationContract]
13: string SayHello();
14: }
15:
And this is the implementing class:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Runtime.Serialization;
5: using System.ServiceModel;
6: using System.Text;
7: using System.Security.Permissions;
8: using System.Security;
9:
10:
11: public class HelloService : IHelloSayer
12: {
13:
14: public string SayHello()
15: {
16: //block unauthenticated users, can't use the [principalpermission] attribute for this
17: if (!OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.IsAuthenticated)
18: {
19: throw new SecurityException();
20: }
21: return "Hello: " + OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
22: }
23:
24: }
Note the part to block unauthenticated users, I can’t use the [PrincipalPermission] attribute for this because I haven’t configured any role based security and therefore the calling thread’s current principal is not set. If you have a lot of methods, you could of course write your own behavior for this.
WCF Configuration
What’s more important, is the configuration file that is going to enable the security for my service. Well, here it is:
1: <system.serviceModel>
2: <services>
3: <service behaviorConfiguration="ServiceBehavior" name="HelloService">
4: <host>
5: <baseAddresses>
6: <add baseAddress="https://localhost"/>
7: </baseAddresses>
8: </host>
9: <endpoint address="" binding="basicHttpBinding" bindingConfiguration="test" contract="IHelloSayer">
10: <identity>
11: <dns value="localhost"/>
12: </identity>
13: </endpoint>
14: <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
15: </service>
16: </services>
17: <bindings>
18: <basicHttpBinding>
19: <binding name="test">
20: <security mode="TransportWithMessageCredential">
21: <message clientCredentialType="UserName"/>
22: </security>
23: </binding>
24: </basicHttpBinding>
25: </bindings>
26: <behaviors>
27: <serviceBehaviors>
28: <behavior name="ServiceBehavior">
29: <serviceMetadata/>
30: <serviceDebug includeExceptionDetailInFaults="false"/>
31: <serviceCredentials>
32: <userNameAuthentication userNamePasswordValidationMode="Custom"
33: customUserNamePasswordValidatorType="MyValidator, AuthenticationLib"/>
34: </serviceCredentials>
35: </behavior>
36: </serviceBehaviors>
37: </behaviors>
38: </system.serviceModel>
The most of this should be familiar to WCF developers, so i will highlight only the options that have something to do with Silverlight and Security:
- Line 9: I’m using the basicHttpBinding, this is of course because Silverlight only supports the basicHttpBinding. Note that I use a specific binding configuration named “test”.
- Lines 18-24: This part specifies the “test” binding configuration. Note the security mode. Using Transport only would leave me no options for a custom username / password scenario; basic and digest both don’t play nice with WCF’s custom username / password mechanism. Ideal would be Message security, but the combination of Message security and basicHttpBinding doesn’t support a username / password scenario. That leaves us with one option that supports a WCF custom username / password scenario and can be used with a basicHttpBinding, namely TransportWithMessageCredential. This option means that the credentials are part of the SOAP message, but are stored in plain text. Because storing the credentials in plain text is unsecure, a secure connection is mandatory, for http this means SSL. So this option really is a combination of transport security (SSL) and credentials inside the message, hence the name TransportWithMessageCredential I guess :).
- So far we have configured that we need the credentials to be a part of the message and that the transport of the message must be done using SSL. The only thing we still have to configure is what form of credentials the client must provide. That part is configured in line 21, the value “UserName” actually means “UserNameAndPassWord”, because a client can provide a username and a password.
- Lines 31-34: This section inside a service behavior, specifies how WCF should handle incoming client credentials. Since our credentials take the form of a username / password combination, we use the “userNameAuthentication” element to configure that we want to authenticate the credentials our self. This is achieved by setting the “userNamePasswordValidationMode” to “Custom” and the “customUserNamePasswordValidatorType” to a type that can perform our custom validation. The format is {FullyQualifiedTypeName, AssemblyName}. Take a look at the “MyValidator” class:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using System.IdentityModel.Selectors;
6: using System.Security;
7: using System.IdentityModel.Tokens;
8: using System.ServiceModel;
9:
10: /// <summary>
11: /// Custom username / password validator
12: /// </summary>
13: public class MyValidator : UserNamePasswordValidator
14: {
15: public MyValidator()
16: {
17:
18: }
19:
20: public override void Validate(string userName, string password)
21: {
22: if (userName != "Alex" || password != "vanbeek")
23: {
24: throw new FaultException("Invalid username / password combination!");
25: }
26:
27: }
28: }
This is a very simple example, but I might have used ADO.NET to check the credentials in a database or any other way to authenticate the credentials. A couple of rules for this class:
- In order to be used as a custom username / password validator, it must be derived from the UserNamePasswordValidator class.
- Perform authentication by overriding the Validate method.
- If validation fails, throw a SecurityTokenException if you want a non informative message or a FaultException if you want a informative message. If validation succeeds, just do nothing.
Binary Message Encoding
More and more people are using the new Silverlight 3 binary message encoding and with good reason. So just for completeness, here is the binding part of the configuration file when using the new BinaryMessageEncoding for Silverlight 3:
1: <customBinding>
2: <binding name="test">
3: <security authenticationMode="UserNameOverTransport"/>
4: <binaryMessageEncoding/>
5: <httpsTransport/>
6: </binding>
7: </customBinding>
The Silverlight application
Take a look at the code of the Silverlight application:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Net;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Documents;
8: using System.Windows.Input;
9: using System.Windows.Media;
10: using System.Windows.Media.Animation;
11: using System.Windows.Shapes;
12: using SecureSilverlightApp.security;
13: using System.Net.Browser;
14:
15: namespace SecureSilverlightApp
16: {
17: public partial class MainPage : UserControl
18: {
19: public MainPage()
20: {
21: WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
22: InitializeComponent();
23: HelloSayerClient hsc = new HelloSayerClient();
24: hsc.ClientCredentials.UserName.UserName = "Alex";
25: hsc.ClientCredentials.UserName.Password = "test";
26: hsc.SayHelloCompleted += new EventHandler<SayHelloCompletedEventArgs>(hsc_SayHelloCompleted);
27: hsc.SayHelloAsync();
28: }
29:
30: void hsc_SayHelloCompleted(object sender, SayHelloCompletedEventArgs e)
31: {
32: if (e.Error != null)
33: {
34: MessageBox.Show(e.Error.InnerException.Message);
35: }
36: else
37: {
38: MessageBox.Show(e.Result);
39: }
40: }
41: }
42: }
It’s a simple application that calls the service on startup. Important lines are:
- Line 21: I’m using the new ClientHttpStack, to prevent the dreaded “NotFound” errors. This way, error messages from the service can flow through to my Silverlight application.
- Lines 24-25: In Silverlight 3 the generated proxies support the new ClientCredentials property. You can use this to set the username and password. You have to set this only once and the credentials get send with every call you make on the proxy. The observative reader will notice that the provided password, is not allowed by my custom password validator. When running this application the message will be:
After changing the password property of the ClientCredentials property of the proxy to the allowed password “vanbeek”, the message will look like this:
This shows that the client’s credentials flow neatly into the WCF service with every call. You can extend this example with declarative role based security by using WCF’s custom role based security mechanism.