Gotchas for Web Services in Silverlight

Recently, I’ve been working on a Silverlight application that grabs some data via web services, and up until the last day or so, those services were on a different domain, and the service domain had a clientaccesspolicy.xml file in place allowing access to the service. Good enough so far.

A couple days ago, I wanted to access some functionality that’s already built into Community Megaphone, so I wrapped it up into a web service, and figured I could call it without a clientaccesspolicy.xml file, since the Silverlight application is also running in the same domain. WRONG!

Why? Because the web service in question is configured to run in SSL mode, and in Silverlight 2, as Tim Heuer explains in this post, calling an https-based web service from an http-based Silverlight app requires a clientaccesspolicy.xml file, even if both are on the same domain.

What threw me about this was that once I started getting errors indicating that a cross-domain problem existed, I did put a clientaccesspolicy.xml file in place. However, as Tim further explains, just having a policy file is not enough, if all you do is this:

<?xml version="1.0" encoding="utf-8"?> 
<access-policy>
   <cross-domain-access>
      <policy>
         <allow-from http-request-headers="Content-Type">
            <domain uri="*"/>
         </allow-from>
         <grant-to>
            <resource path="/" include-subpaths="true"/>
         </grant-to>
      </policy>
   </cross-domain-access>
</access-policy>

Rather, you must explicitly allow both http and https access, as follows:

<?xml version="1.0" encoding="utf-8"?> 
<access-policy>
   <cross-domain-access>
      <policy>
         <allow-from http-request-headers="Content-Type">
            <domain uri="https://*"/>
            <domain uri="https://*"/>
         </allow-from>
         <grant-to>
            <resource path="/" include-subpaths="true"/>
         </grant-to>
      </policy>
   </cross-domain-access>
</access-policy>

Note that while the clientaccesspolicy.xml file listing above solves the http/https access issue, it may not be the best policy file to use, because like the original, it basically says “any domain may access any resources in my site in a cross-domain way.” If you don’t understand why that’s a bad thing, just trust me that you probably don’t want to do that. Instead, you can limit access to specific domains, limit the types of requests that will be allowed, limit the resources that can be accessed, or any combination of these.

As a rule, you want to use a policy file that limits access as much as possible while allowing the activity you’re looking for, since that will minimize the potential for misuse.

So if I only wanted to allow requests from www.communitymegaphone.com, I could do the following:

<?xml version="1.0" encoding="utf-8"?> 
<access-policy>
   <cross-domain-access>
      <policy>
         <allow-from http-request-headers="Content-Type">
            <domain uri="https://www.communitymegaphone.com"/>
            <domain uri="https://www.communitymegaphone.com"/>
         </allow-from>
         <grant-to>
            <resource path="/" include-subpaths="true"/>
         </grant-to>
      </policy>
   </cross-domain-access>
</access-policy>

And if I only wanted to allow access to SOAP-based web services, I could do the following:

<?xml version="1.0" encoding="utf-8"?> 
<access-policy>
   <cross-domain-access>
      <policy>
         <allow-from http-request-headers="SOAPAction">
            <domain uri="https://*"/>
            <domain uri="https://*"/>
         </allow-from>
         <grant-to>
            <resource path="/" include-subpaths="true"/>
         </grant-to>
      </policy>
   </cross-domain-access>
</access-policy>

Finally, if I wanted to only allow access to resources in a specific folder, I could do the following:

<?xml version="1.0" encoding="utf-8"?> 
<access-policy>
   <cross-domain-access>
      <policy>
         <allow-from http-request-headers="Content-Type">
            <domain uri="https://*"/>
            <domain uri="https://*"/>
         </allow-from>
         <grant-to>
            <resource path="/myservicepath" include-subpaths="true"/>
         </grant-to>
      </policy>
   </cross-domain-access>
</access-policy>

Note that if you do not set include=subpaths to “true” when using a folder name, none of the resources in that folder will be accessible. You can also include the filename if you want to limit access to a single file.

For additional information on cross-domain policy files, you can check out this video, and Tim’s cross domain policy file helpers for Visual Studio.

One last tip…if you’re trying to call something cross-domain from Silverlight, and having trouble, I’d highly recommend running an http proxy such as Fiddler to see what calls are being made, and whether a clientaccesspolicy.xml file is being returned when requested. You can also use this handy tool from Frank La Vigne to determine whether a given domain returns a clientaccesspolicy.xml file.

UPDATE: Tim Heuer let me know that you always need to include the http-request-headers attribute on the allow-from element, and I've updated the examples to reflect this.