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.
Note: this tutorial has been updated for Silverlight 2 RTW
(Series Links: Part 1, Part 2, Part 3)
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 once a user logs in, 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 way, even though cookies and auth are always sent, 3rd party applications cannot call (authenticated) web services on the main site.
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 that 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 & allows Content-Type header to be sent looks like:
1: <?xml version="1.0" encoding="utf-8"?>
2: <access-policy>
3: <cross-domain-access>
4: <policy >
5: <allow-from http-request-headers="Content-Type">
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>
14:
Specifying WHO is allowed by a policy
Within a policy, you specify allowed domains by:
- a specific domain (e.g. ‘http://bar.com’, ‘https://bar.com’)
- Represents only the domain with that specific protocol, host name & port.
- subdomain wildcard (e.g. ‘http://*.bar.com’)
- Represents all subdomains
- http wildcard (i.e. ‘http://*’)
- Represents all http:// domains
- general wildcard (i.e. ‘*’)
- Represents all domains (http:// & https://), if an http service
- Represents all secure domains (https://), if an https service
Note: If your service is an HTTPS service, you need to explicitly allow insecure HTTP callers with the ‘http://*’ literal. Otherwise, allowing ‘*” will only grant access to secure HTTPS callers.
Specifying WHAT is allowed by a policy
Also, within a policy, you specify the granted resources on the server by either:
- Supplying a path and setting include-subpaths to be false. This means that only requests that exactly match the specified path are allowed.
- Supplying a path and setting include-subpaths to be true. This mean requests whose paths are prefixed by the specified path are allowed.
Specifying HOW a request can be sent
By default, no request headers can be sent. You can designate which request headers should be allowed on POSTs by:
- “*” wildcard representing all non-blacklisted headers
- a comma seperated list of headers (e.g. “SOAPAction, Content-Type”)
It’s also possible to specify more than one policy within a policy file.
Examples
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 http-request-headers="SOAPAction">
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:
- http://sub.cool.com, http://partner.com, and http://friend.com.
They can send on POSTs:
- SOAPAction request headers
- Content-Type request headers. (Is allowed by default.)
They have access to:
- http://cool.com/shipments and all its subpaths (e.g. http://cool.com/shipments/, http://cool.com/shipments/details.xml, etc).
- http://cool.com/shipments/creditcards
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…
- they come from an app on http://sub.com.com and are requesting http://cool.com/partners/feeds/favorites.rss
- OR they are for any subpath of http://cool.com/api
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!
This is great stuff! But why do people insist on posting sample code with line numbers at the left? If i want to copy the code, then I have to go and remove all the line numbers before I can use it!
I understand that challenges and appreciate the initial work done heere to get cross-domain requests up and running. However, in-message authentication is – by nature – a security risk. Until HTTPS and Basic/Digest is properly supported, it is much wiser to simply *not* expose private content via cross-domain requests.
Great article! Thanks.
Minor bug: It should be “uri” in example 2 (“i” missing).
Question: Is there a way to explictly deny access to a subpath while giving access to a path.
Great!
Can I translate to Japanese, and carry to my blog?
I would like to see an code example using YouTube or at least know why it cannot be created.
In example 1 you have the http-request-headers=”Content-Type” in element – is this correct?
http://msdn.microsoft.com/en-us/library/cc197955(VS.95).aspx shows it in the element. Typo – you are also self closing the element.
Excellent catch.. yes, that is a typo. It should be in the tag. I’ve updated this post.
crossdomain.xml not working:
I have created a simple service and hosted it on my local IIS.
when i try to access it from another site i get 404 error.
ClientAccessPolicy.xml works great, but if i remove it an use only crossdomain.xml it doesn’t.
Why is that?
I have used you format of crossdomain.xml and the one posted at http://msdn.microsoft.com/en-us/library/cc645049(VS.95).aspx#cross_domain_policy
Pingback:Recent Links: ASP.NET, ASP.NET AJAX, ASP.NET MVC, Silverlight « Tad Wang’s Weblog