Catching the market in the late evening, after another day of seattle sprinkle.
Here’s the final post in this 3 part Silverlight HTTP networking series.
In the first post, we discussed basic site of origin HTTP communication. In the second, we gave an overview of Silverlight’s cross domain communication support.
Today, we’ll drill in to how to configure your web service to enable Silverlight cross domain callers. If you haven’t already, please be sure to read (at least) part 2 of this series - it provides a lot context for this discussion.
So, You Want To Make Your Web Service Callable By 3rd Parties…
Exposing your web service to 3rd parties is an important decision that should be done with care and diligence. As you’re designing your web service APIs, please keep the following in mind:
1. Using cookies/auth on your main site? Create a separate domain for your 3rd party-accessible APIs
In Silverlight, we send cookies & authentication with each cross domain request. Because of this, web service authors need to be careful to separate their 3rd party accessible APIs from their main site.
Let’s look at example. http://cool.com uses cookie authentication. Once a user is logged on, she can access and update her public profile as well as her personal account information.
Now, http://cool.com wants to enable 3rd party apps to access public profile information. However, they don’t want to these 3rd party apps to get at a user’s billing information.
If the primary site and the web service is hosted on the one domain, then the same cookies & auth are sent from http://cool.com applications well as 3rd party applications.
This could allow 3rd party sites to access protected information on the user’s account.
Instead, the web service apis should be hosted on a separate domain.
This enforces that different cookies and auth are used for the main web site and the web services server.
Do you want to expose your web service to more than one partner, or to the public at large? If so, then basic/digest authentication and/or cookies aren’t sufficient for web service authentication. Which leads us to…
2. Requiring 3rd party apps to authenticate? Use an in-message authentication approach.
You should not rely on cookies or basic/digest authentication to authenticate a request on a 3rd party exposed API. This is because multiple 3rd parties will be using the same APIs, and the same cookies and/or auth will be sent to http://api.cool.com regardless of the specific caller.
Let’s say a user trusts http://bar.com and authenticates to http://api.cool.com from http://bar.com/app.xap. That same user does NOT trust http://foo.com.
However, since http://api.cool.com uses cookie authentication, once the user has signed in, she is signed in for all calls from that browser session, not just a particular site. This means http://foo.com/app.xap can access private information, even though that wasn’t the user’s intent.
If you want to authenticate the particular application calling a web service method, it is better to use an in message authentication. For instance, you could specify the particular application’s key as a parameter in the query string.
With an in-message approach to authentication, you can determine which 3rd party application is calling your web service.
3. Set the cache policy on your cross domain policy file
As explained last time, we use the browser plugin networking APIs to issue requests for the cross domain policy file. This means that the normal browser rules around request caching apply to the policy file.
We recommend that you do NOT allow the browser to cache the policy file - this makes changing the policy file at later time much easier. Turning off client caching is done by configuring your server to set a "Cache-Control:no-cache" response header on the policy file.
Remember, we only check a site’s cross domain policy once per application session, so the bandwidth/latency cost of not caching the policy file is limited.
4. Restrict the policy as much as possible
The last piece of advice is the easiest, but the one most prone to copy & paste mistake: don’t open up a web service to everyone unless you *need* everyone to be able to call it.
If you’re just trying to enable certain particular partner apps to call your web service, allow access to that handful of domains.
Similarly, if you only really want public callers on a certain path, then just open up that subpath.
That being said, don’t configure a policy with 400 different <resource> tags - as that is also difficult to audit and maintain. As always, good security is a balance between reducing surface area and maintaining simplicity.
Configuring a Silverlight Policy File
The Silverlight policy file is called clientaccesspolicy.xml. A Silverilght policy file that opens up an entire domain to the public looks like:
1: <?xml version="1.0" encoding="utf-8"?>
2: <access-policy>
3: <cross-domain-access>
4: <policy>
5: <allow-from>
6: <domain uri="*"/>
7: </allow-from>
8: <grant-to>
9: <resource path="/" include-subpaths="true"/>
10: </grant-to>
11: </policy>
12: </cross-domain-access>
13: </access-policy>
Within a policy, you specify allowed domains by:
Also, within a policy, you specify the granted resources on the server by either:
It’s also possible to specify more than one policy within a policy file.
Here are some more clientaccesspolicy.xml examples:
Example1: http://cool.com/clientaccesspolicy.xml:
1: <?xml version="1.0" encoding="utf-8"?>
2: <access-policy>
3: <cross-domain-access>
4: <policy>
5: <allow-from>
6: <domain uri="http://sub.cool.com/"/>
7: <domain uri="http://partner.com"/>
8: <domain uri="http://friend.com"/>
9: </allow-from>
10: <grant-to>
11: <resource path="/shipments" include-subpaths="true"/>
12: <resource path="/creditcards" />
13: </grant-to>
14: </policy>
15: </cross-domain-access>
16: </access-policy>
The above policy allows requests coming from Silverlight applications on:
They have access to:
Note: The allowed domains do NOT have access to http://cool.com/creditcards/ or http://cool.com/creditcards/numbers.xml, as the "/creditcards" resource tag does not have an include-subpath="true" attribute.
Example2: http://cool.com/clientaccesspolicy.xml:
1: <?xml version="1.0" encoding="utf-8"?>
2: <access-policy>
3: <cross-domain-access>
4: <policy>
5: <allow-from>
6: <domain ur="http://sub.cool.com"/>
7: </allow-from>
8: <grant-to>
9: <resource path="/partner/feeds/favorites.rss" />
10: </grant-to>
11: </policy>
12: </cross-domain-access>
13: <cross-domain-access>
14: <policy>
15: <allow-from>
16: <domain ur="*"/>
17: </allow-from>
18: <grant-to>
19: <resource path="/api" include-subpaths="true"/>
20: </grant-to>
21: </policy>
22: </cross-domain-access>
23: </access-policy>
The above policy file contains two policies. Requests are allowed if…
Note: If you’re trying to create your own policy file, Tim Heuer has written a blog post that helps you get VS intellisense when authoring a clientaccesspolicy.xml.
Configuring a Silverlight-Supported Flash Policy File
The Flash policy file is called crossdomain.xml. Silverlight supports crossdomain.xml files that allow public access to an entire domain. Specifically, this mean supporting crossdomain.xml of the format:
1: <?xml version="1.0"?>
2: <!DOCTYPE cross-domain-policy SYSTEM
3: "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
4: <cross-domain-policy>
5: <allow-access-from domain="*"/>
6: </cross-domain-policy>
So, that’s our 3 part HTTP networking series. Hope you found it useful. Like always, we love to hear your feedback.
Can’t wait to see the applications you’re going to build!
Last time, we discussed how Silverlight applications talk to their site of origin server. In this post, we’ll dive into the high level concepts around Silverlight’s cross domain HTTP communication. Then next time, we’ll go into details of how to set up a cross domain policy file.
(I know - I lied! But part 2 was just getting too long… so I converted this to a 3 part series.)
What is Cross Domain Communication?
As mentioned last time, your Silverlight application by default can only talk back to its site of origin server. This is in line with the general browser sandbox, and is designed to prevent cross site forgery.
The site of origin server is determined by the app’s deployment URI: the location of the XAP for managed applications, the address of the XAML page for javascript applications.
If a HTTP request matches the origin server’s domain, protocol, and port number, then it’s considered a site of origin request and is allowed. Otherwise, it’s classified as a cross domain call.
Silverlight’s Cross Domain Policy Support
In order for a cross domain request to a particular web service to succeed, that web service needs to explicitly opt-in to 3rd party callers.
In Silverlight 2, the primary way of enabling cross domain calls is through a policy file placed at the root of the server. We support two types of policy files:
What Can You Say to a Cross Domain Server?
Basic Capabilities
So, you’ve got permission from the appropriate policy file to make a cross domain request . What can you do with that request?
Also, all the requests you send will have have cookies and authentication sent with them. This is a significant point, and we’ll dig into it deeper in the part 3 of this post series.
The above capabilities are determined by the browser plugin’s networking APIs. Please see Part 1 for a deeper discussion of networking stack implementation.
Path Character Restrictions
Silverlight has placed certain restrictions on the path portion of a cross domain URI.
Specifically, a cross domain request path can ONLY contain:
This restriction was done to prevent attacks around ".." and %-encoded ".."s, which malicious apps could use to try to escape out of an allowed path into a disallowed path. For Beta1, we decided to start by being extra conservative in the characters we allowed and then get feedback from you.
Does this character restriction prevent you from accomplishing your scenarios? We’ve heard not allowing ‘;’ is a big problem. Others? What support do you need?
Where does Silverlight Look for the Cross Domain Policy File?
When a request is detected as a cross domain request, we look first for a Silverlight policy file at the root of the request’s server.
| Request URI | Policy File Checked |
| http://foo.com/bar/data | http://foo.com/clientaccesspolicy.xml |
| http://sub.foo.com/bar/data | http://sub.foo.com/clientaccesspolicy.xml |
| http://foo.com:8080/bar/data | http://foo.com:8080/clientaccesspolicy.xml |
If such a clientaccesspolicy.xml file does not exist, we then look for a crossdomain.xml file at the same location.
Note: Certain servers do not return a 404 for a missing clientaccesspolicy.xml but a custom error page. Starting in Beta2, all malformed clientaccesspolicy.xml’s will trigger a fallback to the look for the crossdomain.xml
"Caching" Of Policy Files
Silverlight looks up the cross domain policy file for a particular server ONCE per application session. (Where an application session is the lifetime of a particular xap or xaml page instance in memory.)
Also, we use the browser networking stack to issue the request for the policy file, so the normal browser caching of the request happens under the covers.
Redirects
Redirects for the policy file itself are not allowed.
Request URIs redirects will only be successful if the original and final URIs are allowed via the appropriate cross domain policy. (Note: The browser handles handles the actual redirect logic itself and ensures it matches w3 spec.)
So that’s the high level cross domain behavior overview. Next time, we’ll drill into how to set up a cross domain policy file.
I just started a "Digital Photography 2" course at the Photographic Center Northwest. I’ve had two classes so far and am liking it a lot (though having 10+ hours of homework a week is both LOTS Of fun… and a bit tricky).
The first assignment was to photograph 15 letters - but not real letters.
Here are the results of my first assignment - can you guess which letter is which? (You can click through to my flickr site to see if you’re right.)
Random: I just finished last week’s New Yorker on the bus.
Did you know that…
Just crazy.
Ah, the way the world works.
This is going to be a two parter. :) Today, we’ll dig into the core networking capabilities of the HTTP stack for site of origin communication. Next time, we’ll go deep into understanding our HTTP cross domain support.
update: this is now a 3 part series
So, You Want To Phone Home…
In Silverlight 2, by default, you are only able to talk back to your site of origin server. This is in line with the general browser sandbox, and is designed to prevent cross site forgery.

To determine if a HTTP request is going back to site of origin, we look at the deployment URI of the XAP and the request URI. Comparing these two URIs, 3 things must match:
If all three things match, the request will be allowed to go out into the world. If they don’t, by default, we’ll disallow the request and throw an exception. (Cross domain policies will be covered in part 2 of this post series.)
What Can You Say to Your Site of Origin Server?
You can talk to your site of origin server with the below capabilities:
Also, all the requests you send will have the “right thing happen” to them from a cookies and authentication standpoint. This is a result of us leveraging the hosting browser to make the HTTP request.
HTTP requests themselves are:
* We’re investigating to see if we can relax this latter restriction.
Why Those Capabilities?
The above capabilities are the result of:
To implement our Silverlight HTTP stack, we use the browser plug-in’s networking APIs. The HTTP capabilities we expose are therefore bound by the common set of capabilities exposed by the browsers that Silverlight supports.
Note: There are other ways we could have implemented the networking stack. For instance, we could have proxied all calls through the browser’s XmlHttpRequest object. However, this would have imposed a site of origin restriction on the requests, and we wanted to enable cross domain communication to existing web services. Similarly, we could have gone directly to the operating system’s networking APIs, but then we would have lost the cookies and authentication integration with the browser. In the future, exploring multiple of these stacks to expose more capabilities is definitely a possibility, and is something we would like feedback on.
What APIs Should I Use?
There are two APIs in Silverlight for HTTP communication. The first is WebClient and the second is HttpWebRequest.
WebClient
WebClient is a great API with a simple, events-based paradigm. You can use it to easily download a string or a Stream.
WebClient, behind the scenes, does a GET request and then gives you back the result in the form you wanted. It also automatically resolves relative URIs against the deployment URI of the XAP.
Sample: Downloading a string using WebClient
1: private void DownloadString()
2: {
3: WebClient webClient = new WebClient();
4:
5: // Hook up events
6: webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
7: webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
8:
9: // Initiate download
10: webClient.DownloadStringAsync(new Uri("myfeed.xml", UriKind.Relative));
11: }
12:
13: void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
14: {
15: // Update progress UI
16: progressTextBox.Text = e.ProgressPercentage;
17: }
18:
19: void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
20: {
21: if (e.Error == null)
22: {
23: // Clear progress UI and show downloaded string
24: progressTextBox.Text = "";
25: feedTextBox.Text = e.Result;
26: }
27: }
28:
HttpWebRequest & HttpWebResponse
HttpWebRequest and HttpWebResponse are the standard .NET HTTP apis. They are much more powerful than WebClient but also more complex to use. You should use them when you want to set headers or issues a POST request.
Sample: Sending POST request using HttpWebRequest
1: private void SendPostRequest()
2: {
3: HttpWebRequest request = WebRequest.Create(new Uri("http://foo.com/upload")) as HttpWebRequest;
4: request.Method = "POST";
5:
6: // Set ContentType through property
7: request.ContentType = "application/xml";
8:
9: // Set other headers through Headers property
10: request.Headers["x-custom-header"] = "value";
11:
12: // Initiate getting request stream
13: IAsyncResult asyncResult =
14: request.BeginGetRequestStream(new AsyncCallback(RequestStreamCallback), request);
15: }
16:
17: private void RequestStreamCallback(IAsyncResult ar)
18: {
19: HttpWebRequest request = ar.AsyncState as HttpWebRequest;
20:
21: // populate request stream
22: request.ContentType = "text/xml";
23: Stream requestStream = request.EndGetRequestStream(ar);
24: StreamWriter streamWriter = new StreamWriter(requestStream, Encoding.UTF8);
25: streamWriter.Write("<?xml version="1.0"?>"
26: + "<entry xmlns="http://www.w3.org/2005/Atom">"
27: + "<author>"
28: + "<name>Elizabeth Bennet</name>"
29: + "<email>liz@gmail.com</email>"
30: + "</author>"
31: + "<title type="text">Entry 1</title>"
32: + "<content type="text">This is my entry</content>"
33: + "</entry>");
34: streamWriter.Close();
35:
36: // Make async call for response
37: request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
38: }
39:
40: private void ResponseCallback(IAsyncResult ar)
41: {
42: HttpWebRequest request = ar.AsyncState as HttpWebRequest;
43: WebResponse response = request.EndGetResponse(ar);
44:
45: // use response.
46: }
Feedback – We Love It
Do the capabilities of our HTTP stack satisfy your needs? What do you think of the API set? We’d love to hear your thoughts.
Stay Tuned for Cross Domain Support in Part 2….