I'm cross-posting on the mozilla services weblog. Since this is the first time we're doing that, I though it could be useful to point you there. Check it out and expect more technical articles there in the future.
For security reasons, it's not possible to do cross-domain requests. In other words, if you have a page served from the domain lolnet.org, it will not be possible for it to get data from notmyidea.org.
Well, it's possible, using tricks and techniques like JSONP, but that doesn't work all the time (see the section below). I remember myself doing some simple proxies on my domain server to be able to query other's API.
Thankfully, there is a nicer way to do this, namely, "Cross Origin Resource-Sharing", or CORS.
If you want to use CORS, you need the API you're querying to support it; on the server side.
The HTTP server need to answer to the OPTIONS verb, and with the appropriate response headers.
OPTIONS is sent as what the authors of the spec call a "preflight request"; just before doing a request to the API, the User-Agent (the browser most of the time) asks the permission to the resource, with an OPTIONS call.
The server answers, and tell what is available and what isn't:
So, if you want to access the /icecream resource, and do a PUT there, you'll have the following flow:
> OPTIONS /icecream > Access-Control-Request-Methods = PUT > Origin: notmyidea.org < Access-Control-Allow-Origin = notmyidea.org < Access-Control-Allow-Methods = PUT,GET,DELETE 200 OK
You can see that we have an Origin Header in the request, as well as a Access-Control-Request-Methods. We're here asking if we have the right, as notmyidea.org, to do a PUT request on /icecream.
And the server tells us that we can do that, as well as GET and DELETE.
I'll not cover all the details of the CORS specification here, but bear in mind than with CORS, you can control what are the authorized methods, headers, origins, and if the client is allowed to send authentication information or not.
CORS is not an answer for every cross-domain call you want to do, because you need to control the service you want to call. For instance, if you want to build a feed reader and access the feeds on different domains, you can be pretty much sure that the servers will not implement CORS, so you'll need to write a proxy yourself, to provide this.
Secondly, if misunderstood, CORS can be insecure, and cause problems. Because the rules apply when a client wants to do a request to a server, you need to be extra careful about who you're authorizing.
You may know the JSONP protocol. JSONP allows cross origin, but for a particular use case, and does have some drawbacks (for instance, it's not possible to do DELETEs or PUTs with JSONP).
Okay, things are hopefully clearer about CORS, let's see how we implemented it on the server-side.
Cornice is a toolkit that lets you define resources in python and takes care of the heavy lifting for you, so I wanted it to take care of the CORS support as well.
In Cornice, you define a service like this:
from cornice import Service foobar = Service(name="foobar", path="/foobar") # and then you do something with it @foobar.get() def get_foobar(request): # do something with the request.
To add CORS support to this resource, you can go this way, with the cors_origins parameter:
foobar = Service(name='foobar', path='/foobar', cors_origins=('*',))
Ta-da! You have enabled CORS for your service. Be aware that you're authorizing anyone to query your server, that may not be what you want.
Of course, you can specify a list of origins you trust, and you don't need to stick with *, which means "authorize everyone".
You can define the headers you want to expose for the service:
foobar = Service(name='foobar', path='/foobar', cors_origins=('*',)) @foobar.get(cors_headers=('X-My-Header', 'Content-Type')) def get_foobars_please(request): return "some foobar for you"
I've done some testing and it wasn't working on Chrome because I wasn't handling the headers the right way (The missing one was Content-Type, that Chrome was asking for). With my first version of the implementation, I needed the service implementers to explicitely list all the headers that should be exposed. While this improves security, it can be frustrating while developing.
So I introduced an expose_all_headers flag, which is set to True by default, if the service supports CORS.
When you do a preflight request, the information returned by the server can be cached by the User-Agent so that it's not redone before each actual call.
The caching period is defined by the server, using the Access-Control-Max-Age header. You can configure this timing using the cors_max_age parameter.
We have cors_headers, cors_enabled, cors_origins, cors_credentials, cors_max_age, cors_expose_all_headers … a fair number of parameters. If you want to have a specific CORS-policy for your services, that can be a bit tedious to pass these to your services all the time.
I introduced another way to pass the CORS policy, so you can do something like that:
policy = dict(enabled=False, headers=('X-My-Header', 'Content-Type'), origins=('*.notmyidea.org'), credentials=True, max_age=42) foobar = Service(name='foobar', path='/foobar', cors_policy=policy)
I was curious to have a look at other implementations of CORS, in django for instance, and I found a gist about it.
Basically, this adds a middleware that adds the "rights" headers to the answer, depending on the request.
While this approach works, it's not implementing the specification completely. You need to add support for all the resources at once.
We can think about a nice way to implement this specifying a definition of what's supposed to be exposed via CORS and what shouldn't directly in your settings. In my opinion, CORS support should be handled at the service definition level, except for the list of authorized hosts. Otherwise, you don't know exactly what's going on when you look at the definition of the service.
There are a number of good resources that can be useful to you if you want to either understand how CORS works, or if you want to implement it yourself.
Of course, the W3C specification is the best resource to rely on. This specification isn't hard to read, so you may want to go through it. Especially the "resource processing model" section