Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Restricting Access to your WCF Service to a known Silverlight Client

Pete Brown - 15 July 2009

Lately I’ve worked with a number of customers who are familiar with non-service-based ways of accessing their business logic. They typically come from one of two backgrounds:

  • Client developers where the only connection out of the application is to the database.
  • Web developers where all the logic exists on the server

In both cases, they’re not typically working with physical application tiers, even if they logically separate their application. Internal n-tier/client-server developers typically don’t bother thinking about this problem, and Ajax devs, due to often being on the internet, paved this ground a while back.

In a simplified version, this is what the typical breakdown looks like:

image

The non-ajax web app gets to hide everything on the server, the client app “hides” everything on the client. The Silverlight app (and the same is true of ajax apps), however, must expose a service between the server and client. And thanks to standards, most everyone has the technology to access that service.

Restricting Service Access

I’ve had a couple customers request ways of restricting who can access their service. The usual response is to require authentication and HTTPs to ensure you have a private channel between the client and server. If you can go that route, I highly recommend it, as it is a harder nut to crack.

Some of these customers, however, require support for add-ins. Those add-ins would run in the same security context as the primary Silverlight application. The customer wants to discourage direct service access in these cases, and require their customer to use their own specialized API. The primary reason is to enable a multi-tier scenario while keeping their own code and interfaces flexible enough so that they may change service calls over time without worrying about breaking customer code. As we all know, you can tell a customer something is unsupported, but if they write a bunch of code using that unsupported interface and you break it with an upgrade, you’ll have a real mess on your hands.

ClientAccessPolicy and CrossDomain policy files will protect the server from other Silverlight and Flash applications, but do nothing to dissuade access from add-ins or external desktop applications.

IMPORTANT: This is not a reliable pattern for overall security or authentication – not even close. This is a way to prevent casual direct use of your services by otherwise trusted consumers. Don’t rely on this to protect sensitive data, or the integrity of your data store. Always check the data server-side and never expose an unsecured service that allows access to sensitive data.

Requirements

The requirements I’m attempting to meet, for the reasons stated above, are:

  • Support unrestricted service-access from core application code
  • Require (or at least highly encourage) customer or third-party add-in code to use the client API rather than going directly to a service
  • Strongly discourage access from external code (desktop apps, other web sites etc.)

With those key requirements in mind, one logical solution is to use key-based access to the services. A simplified version of the access would look something like this:

image

The steps are:

  1. Web page requests a new key
  2. Web page sets the key in the silverlight client via generated markup
  3. Silverlight calls a web service, passing the key as a parameter
  4. The service validates the key
  5. The service returns the results, if key is valid

Generating and storing the Key in ASP.NET

We’re going to take a key-based approach to enabling access to the server. Server-side, you’ll need to generate and store a key that can be used for access. The key should not persist longer than some amount of time you think is reasonable (a day, maybe hours, maybe just an asp.net session or maybe all keys get purged every night at midnight).

Session isn’t the best place to store this as you now tie the client side application to the lifetime of a server-side session. A better option is to store the value in a database and then restrict access to that to an account used only on the server to store and retrieve those keys.

That said, I’m going to put they key in session right now as this otherwise simple example would require a codeplex site to store all the moving parts if I did it the the more robust way. I don’t recommend this practice for production use. Use an external data store such as a database or shared service.

The AccessKeyRepository class

This class resides on the server, and is used by both asp.net and WCF.

public class AccessKeyRepository
{
    private const string KeyKey = "ServiceAccessKey";
    
    public string GetNewKey()
    {
        // use any algorithm you want here, as long as it
        // generates reasonably hard-to-guess keys. They
        // don't really need to be unique unless you plan
        // to use them for more than just service access

        string key = Guid.NewGuid().ToString();

        StoreKey(key);

        return key;
    }

    private void StoreKey(string key)
    {
        // don't put this in session in your own code,
        // store it in an external location (database, or
        // shared service) that is usable both from WCF
        // and from asp.net, without forcing WCF to 
        // use session, and without tying the client
        // to an asp.net server session
        HttpContext.Current.Session[KeyKey] = key;
    }

    public bool ValidateKey(string key)
    {
        // see above note on session
        if (HttpContext.Current.Session[KeyKey] == key)
        {
            return true;
        }
        else
        {
            // you may wish to log the access attempt or
            // otherwise handle this differently
            return false;
        }
    }
}

The next step is to call the GetNewKey method from this class, and use it to generate the initialization parameters for the Silverlight application

Next we’ll create the services in the asp.net project.

Creating the Services

I used the Silverlight-enabled WCF service template in Visual Studio. If you’re going to create a Soap-only service, I find this approach to be more flexible (and support better serialization) than the .asmx service route. The template makes it just as easy to implement as a .asmx, and you have the ability to extract an interface later should you want to.

[ServiceContract(Namespace = "urn:com.irritatedvowel.samples.servicekey")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class ExampleService
{
    private const string InvalidKeyErrorMessage = "Key is not valid for service access.";

    [OperationContract]
    public void DoSomething(string serviceKey)
    {
        if (!ValidateKey(serviceKey))
        {
            throw new ArgumentException(InvalidKeyErrorMessage, "serviceKey");
        }


        // actually do something with the service

    }

    [OperationContract]
    public string GetSomething(string serviceKey, int value)
    {
        if (!ValidateKey(serviceKey))
        {
            throw new ArgumentException(InvalidKeyErrorMessage, "key");
        }


        // actually do something with the service

        return string.Format("The value you provided is {0}", value);
    
    }

    private bool ValidateKey(string key)
    {
        AccessKeyRepository rep = new AccessKeyRepository();
        return rep.ValidateKey(key);
    }

}

The above service has two public methods that can be called from the client. Both of them do key validation as a guard up-front. I chose to throw an argument exception when the key check fails. You may want to define your own purpose-built exception, or handle another way such as an out param or service return value.

To enable testing the service outside of the context of the client, you may want to provide an additional method that returns the key to your test code – similar to what we did in the asp.net page above, but in a service method instead. Just before to remove it (or host it in another service in same web app and don’t deploy that service) before you go to production.

Next we’ll go back to the Silverlight client and wire it up to receive the service key

Supplying the key to Silverlight using initParams

The asp.net code we need to write is pretty simple. It relies on our AccessKeyRepository defined above and simply calls the method to generate a key and supply that to the Silverlight client.

ASP.NET Code

In the ASP.NET code, I have a single method called GetInitParams(). This method returns the name/value pairs for use in the object tag.

private const string InitParamsKey = "serviceKey";
protected string GetInitParams()
{
    AccessKeyRepository rep = new AccessKeyRepository();
    string key = rep.GetNewKey();

    // init params are comma-separated key=value pairs.
    string initParams = string.Format("{0}={1}", InitParamsKey, key);

    return initParams;
}

ASP.NET Markup

Once I have that method set up in the server-side code, I call it from within the initParams parameter value in the Silverlight object tag.

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
  <param name="source" value="ClientBin/PeteBrown.ServiceKey.xap"/>
  <param name="onError" value="onSilverlightError" />
  <param name="background" value="white" />
  <param name="initParams" value="<% = GetInitParams() %>" />
  <param name="minRuntimeVersion" value="3.0.40623.0" />
  <param name="autoUpgrade" value="true" />
  <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40623.0" style='text-decoration:none'>
      <img src='http://go.microsoft.com/fwlink/?LinkId=108181' alt='Get Microsoft Silverlight' style='border-style:none'/>
  </a>
</object>...

You can, of course, apply the same concepts to something like ASP.NET MVC. Just stuff the initparams in when you generate the object tag. Similarly, if you’re using another server-side technology that supports both pages and services, you can apply the same concepts there. There’s nothing inherently magical about using asp.net here.

Retrieving and storing the key in Silverlight

Initialization Parameters are available from only one place in Silverlight: the Application_Startup event. That means you’ll need to read them there and store them someplace where the service code can use the values, but the add-ins and other code cannot.

For that reason, I decided to put the parameters into the service layer. The downside here is you’ll need something you can set at startup, but which will hang around for the duration of the application. That typically means some sort of singleton object. Why is that a downside? Service access code is one place where you may want to inject an alternate implementation for testing or decoupling of your code.

Remember, we can’t put the key someplace where the whole application will have access to it because the current approach of doing add-ins in Silverlight does not sandbox the add-ins. If we get sandboxing in the future, we can investigate other approaches.

That said, let's look at the available options:

1. Singleton service access classI object to this approach because it forces you to have a single service access class for all your service methods. I don’t think the problem we’re solving here is big enough to dictate that level of architectural constraint in your application.
2. Singleton key-holder class in external assembly with internal accessorIf you can live with the restriction of a singleton class, this is probably the most livable approach – assuming that you’re going to expose an API to your add-ins anyway, having all of this in an external assembly is probably not a burden. If you weren’t going to externalize your service access, then this approach forces you down a design path more than the problem warrants.
3. Injected single-instance key-holder classThis is nice in that it allows you to decouple, but you’ll run into problems with the interface you use. The interface would need to expose the key via a public accessor in the interface in order to be useful in the service access layer. Exposing it there means it gets exposed generally throughout the application.

If you go this route, you’d need to handle the injection inside the Application_Startup event so you can provide the key to the class instance.
4. Injected single-instance service access classI object to this approach for the same reason I do the hard-coded service access class: it forces you to have a single service access class for all your service methods. In this example, though, you wouldn’t need to expose the accessor for the key – just a setter.

If you go this route, you’d need to handle the injection inside the Application_Startup event so you can provide the key to the class instance.

For this example, I went with option #2. This assumes that you want the client-side api to be in a separate assembly, or at at least ok with that idea. If you’re going to expose the API to add-ins anyway, you’ll need the API or at least the interface definition of that API to be external to your application, so this is a non-issue. Remember, one of our requirements is to provide an API that customers may use rather than going directly against our services.

Creating ClientApi Project

The project that will hold the API, in my example is PeteBrown.ServiceKey.ClientApi. Before we can add the actual client-side API code, we’ll add that class and the ServiceKeyContainer class that implements our choice above.

image

ServiceKeyContainer class in ClientApi assembly

public class ServiceKeyContainer
{
    // Singleton stuff

    private static ServiceKeyContainer _current;
    public static ServiceKeyContainer Current
    {
        get
        {
            if (_current == null)
                _current = new ServiceKeyContainer();

            return _current;
        }
    }

    private ServiceKeyContainer() { }


    // property that holds the key. Note the internal get

    private string _serviceKey; 
    public string ServiceKey
    {
        internal get { return _serviceKey; }
        set { _serviceKey = value; }
    }
}

Again, understand that there are restrictions when you decide to use a singleton class. Please refer to the options table to weigh the options for your own application.

The next step is to initialize the ServiceKeyContainer with the key. We do this in the Application_Startup event in app.xaml.cs, before we initialize any UI.

First, add a reference from the Silverlight client to the ClientApi project. Next, use the correct namespace inside your app.xaml.cs class and then modify the startup event to look like this:

private void Application_Startup(object sender, StartupEventArgs e)
{
    // this key needs to be identical to the one used on the server
    // if you have a number of these, you can share a single class
    // between both client and server and have it expose constants
    // see my blog for an example of sharing classes
    string key = "serviceKey";

    ServiceKeyContainer.Current.ServiceKey = e.InitParams[key];
    
    this.RootVisual = new MainPage();
}

Note that we set the rootvisual last. This is to ensure that the service key is set before you start up any processes which may rely on it.

Sending the key to the Services

In the ClientApi project, add a service reference to the services we created previously. Then create a ServiceProxy class (or put the code in the service client for whatever pattern you prefer to follow) and include the code below.

ServiceProxy class in ClientApi assembly

public class ServiceProxy
{
    public event EventHandler DoSomethingCompleted;

    private ExampleServiceClient BuildExampleServiceClient()
    {
        // I set up the connection in this function because
        // the .ClientConfig file won't be in the main .xap
        // we could copy it there (and into sl test projects)
        // but I don't like that added step. You could add
        // it as a linked file if you want to retain the
        // flexibility of using the .ClientConfig

        BindingElementCollection elements = new BindingElementCollection();
        elements.Add(new BinaryMessageEncodingBindingElement());
        elements.Add(new HttpTransportBindingElement());

        CustomBinding binding = new CustomBinding(elements);

        EndpointAddress address = new EndpointAddress(
            new Uri("http://localhost:13519/Services/ExampleService.svc", UriKind.Absolute));

        ExampleServiceClient client = new ExampleServiceClient(binding, address);

        return client;
    }


    public void DoSomething()
    {
        var client = BuildExampleServiceClient();
        client.DoSomethingCompleted += (s, e) =>
        {
            if (DoSomethingCompleted != null)
                DoSomethingCompleted(this, EventArgs.Empty);
        };

        string serviceKey = ServiceKeyContainer.Current.ServiceKey;
        client.DoSomethingAsync(serviceKey);
    }



    // you can create an event args class with this value
    // in it instead if you prefer to be able to call the
    // LoadSomething method multiple times
    private string _loadSomethingResult;
    public string LoadSomethingResult
    {
        get { return _loadSomethingResult; }
        private set { _loadSomethingResult = value; }
    }

    public event EventHandler LoadSomethingCompleted;

    // On the client, I prefer Load vs. Get, as the calls
    // are async. RIA Services also follows this naming
    // pattern for the same reason.
    public void LoadSomething()
    {
        var client = BuildExampleServiceClient();
        client.GetSomethingCompleted += (s, e) =>
        {
            LoadSomethingResult = e.Result;

            if (LoadSomethingCompleted != null)
                LoadSomethingCompleted(this, EventArgs.Empty);
        };

        string serviceKey = ServiceKeyContainer.Current.ServiceKey;
        client.DoSomethingAsync(serviceKey);
    }

}

What’s missing here? If this were in a class that supporting binding (such as a viewmodel class), I’d implement INotifyPropertyChanged. In addition, this code does not do anything with exceptions returned from the service. Handle those as you handle other exceptions in your code, but don’t simply swallow them.

Note that in the code I make use of lambda expressions to simplify the async service code. I wrote about that in a previous post.

Also note that I created the service client in code. The reasons for that are documented in the code.

That’s it for the code.

Best Practices

Do I consider this type of limitation a best practice? No, not really. I think if you’re going to expose a service, you should expose it so it can be used by others, with the understanding that it will be used by others. But the reality is, many customers won’t put their services out there unless there’s some protection against casual use. Another reality is well-established sites like Facebook use similar patterns for their own API access. That doesn’t make it great, it just makes it common and at least worthy of consideration.

My fear is that folks will use this pattern, outside of this context and without security-specific approaches like SSL, and get lulled into a false sense of security.

What are some of the holes I can think of off the top of my head?

  • Customer code could crawl the DOM and read the value from the initparams in the object tag
  • Customer code could potentially force a post-back via javascript and grab a newly-generated key (which would also invalidate the current key, breaking the application)
  • An external app that wants to use the services could simply load the page via http get, parse out the key, and then make service calls
  • Customer could create their own web page or service and run it on the site under the same session. This page could get the key and return it to any caller. Similarly, if you store the key in a database rather than session (probably a better idea), the customer code could potentially query that table to get the key. However, you can secure that and restrict access much more readily than you can with session.
  • A user running fiddler or a similar proxy could get the key each time the app runs and use it for the duration of that session (entered via some textbox). This is fairly cumbersome, but I’ve seem people jump through serious hoops to get around restrictions in software. If your session management isn’t robust, the user could get this once and then ping the page constantly to keep the session alive, reducing their own burden.

So you can see there are a number holes with this. In the specific context of the problem being addressed by this post, however, I think this can be a useful pattern to dissuade casual use by otherwise trusted customers.

Do you have another approach you like to use, that works with Silverlight or other RIA technologies? If so, I’d love to hear about it.

Source code may be downloaded from here.

     
posted by Pete Brown on Wednesday, July 15, 2009
filed under:      

6 comments for “Restricting Access to your WCF Service to a known Silverlight Client”

  1. Miguel MAderosays:
    Pete,

    This is an interesting approach. I gave a list on security and I'll mention this alternative next time. I know it's important to protect the services and most of the time for Silverlight apps, we end up creating UI Specific services which aren't meant to be consumed from other apps.

    Some other things that can help are:
    1. Using Windows Authentication or Forms Authentication and restrict access to services.
    2. Don't expose the metadata for the services (if someone doesn't know the methods it's a bit harder for them to use them).
    3. Don't use a ClientAccessPolicy if you don' intend to share your services (this would only protect you from SL/Flash apps, but that's good).

    Additional to that, most services will need some sort of authorization mechanism and data level security (e.g. only managers can see the cost of the products, etc), but that's a different topic.

    Some of the drawbacks that I see with the implementation are:

    > Customer code could potentially force a post-back via javascript and grab a newly-generated key (which would also invalidate the current key, breaking the application)
    Also the user can open two tabs invalidating the key in the first one.

    Even if we have a secure channel, avoid replays and let's say we could move the initparams from the DOM to a safer place, someone can still use WinDbg and look for the key while in memory. Once he has the key, he could directly call other services.
  2. Matthew Martinsays:
    Thought provoking-- but it seems to me the only fool proof way to make silverlight secure like this is to only expose services that are safe to call with any client, i.e. where validation is checked server side, etc.

    The only other solutions that come to mind are code signing and oauth, neither of which I understand very well.
  3. shashanksays:
    hi Pete,
    Nice article. I am confused though. It may sound silly, but i don't have a code behind for the Silverlight test page just the aspx with the object tag. I do have a default.aspx and default.cs. Do i convert this default.aspx into a Silverlight page with object tag and the javascript code??

    Please help.
  4. Tuskan360says:
    Surely for this purpose you could simply store a static key in the silverlight code. Send it with each method request, or even better, hash it allong with the message and send that with the service call. That way to get round it they would need to decompile your api.
  5. Petesays:
    @Tuskan260

    A static key compiled into your code would be easily captured using something like Fiddler, and then valid from then on. That's even less secure than the session key approach.

    No decompilation necessary.

    Pete

Comment on this Post

Remember me

1 trackback for “Restricting Access to your WCF Service to a known Silverlight Client”

  1. NewsPeepssays:
    Thank you for submitting this cool story - Trackback from NewsPeeps