Skip to content

Instantly share code, notes, and snippets.

@karanth
Last active January 3, 2016 20:58
Show Gist options
  • Save karanth/8518158 to your computer and use it in GitHub Desktop.
Save karanth/8518158 to your computer and use it in GitHub Desktop.
Notes on CORS - More to it than what meets the eye

A previous [gist] (https://gist.github.com/karanth/8467944#file-jsonp-cors-pub-md) had notes about JSONP and CORS as methods for making cross-domain requests. After consideration, CORS was a secure and a better alternative. However, implementing CORS is not as straightforward as introducing a few headers on the server response.

There CORS W3C spec identifies cases where a pre-flight request is called for in a CORS situation. A pre-flight request is a client-iniated request using the OPTIONS HTTP verb that tries to understand the capabilities of the server. Only on a successful response (200) to the pre-flight request, and presence of certain headers in the server response, will the client initiate the actual CORS request.

A little bit of nosing around with the rapportive plugin revealed a CORS situation, that forces the client to execute the special pre-flight request. Rapportive is a neat browser plugin that gives details about a contact (by looking into the from field of the email) when a google mail email is opened. Rapportive makes a cross-domain call (from Gmail to Rapportive) to fetch contact information.

The first call is a pre-flight request and the browser controls this call. Pre-flight requests are made by the browser in 3 cross-domain scenarios - 1) when there are custom headers in the request 2) the http verb is anything other than GET, POST or HEAD 3) the content-type of the POST data is anything other than text/plain, application/x-www-form-urlencoded or multipart/form-data

The rapportive request sets a custom header field in the request (X-SESSION-TOKEN), whose value is used to pass a session id with each request (for rate-limiting I presume). If the rapportive client code is observed, it will show only the GET request. But on the wire, there are 2 requests. The browser is adding the pre-flight request as defined by the CORS spec. The pre-flight request has the OPTIONS HTTP verb and should contain, 1) Access-Control-Request-Method (value indicates the verb used in the actual request) 2) Access-Control-Request-Headers (value defines the custom headers that will be sent with the actual request). The Origin header value is mandatory for any CORS request pre-flighted or not.

OPTIONS /contacts/email/<contact's email>?if_none_match=d41d8cd98f00b204e9800998ecf8427e&recheck_after=3&polling_active=true&user_email=<my email>&client_version=ChromeExtension+rapportive+1.4.1&client_stamp=1390187755 HTTP/1.1
Host: profiles.rapportive.com
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: https://mail.google.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Access-Control-Request-Headers: accept, x-session-token, content-type
Accept: */*
Referer: https://mail.google.com/mail/u/0/?ui=2&shva=1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8



HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, x-session-token, content-type
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Origin: https://mail.google.com
Access-Control-Max-Age: 86400
Content-Encoding: gzip
Content-Type: text/plain
Date: Mon, 20 Jan 2014 10:11:08 GMT
Server: nginx
transfer-encoding: chunked
Connection: keep-alive

The pre-flight response is an empty body response with a few headers set. The mandatory headers are, 1) Access-Control-Allow-Methods (value indicates the permitted methods in the actual request) 2) Access-Control-Allow-Headers (value indicates the permitted headers in the actual request). Access-Control-Allow-Origin is discussed in the previous post.

There is another interesting header, the Access-Control-Allow-Credentials header whose value is true. Though this is an optional header, it directs the client to use cookies in the request and response. This header along with the withCredentials flag set on XMLHttpRequest object, client-side, allow cookies to be submitted and retrieved by the client when interacting with the server.

Rapportive makes the actual call only after the it receives a 200 response for the pre-flight call.

GET /contacts/email/<contact's email>?if_none_match=d41d8cd98f00b204e9800998ecf8427e&recheck_after=3&polling_active=true&user_email=<my email>&client_version=ChromeExtension+rapportive+1.4.1&client_stamp=1390187755 HTTP/1.1
Host: profiles.rapportive.com
Connection: keep-alive
Accept: */*
Origin: https://mail.google.com
 X-Session-Token: <token value>
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: https://mail.google.com/mail/u/0/?ui=2&shva=1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
If-None-Match: "407b0d2233f0b64c7c794323d7a139b9"


HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Origin: https://mail.google.com
Access-Control-Max-Age: 86400
Cache-Control: private, max-age=0, must-revalidate
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Mon, 20 Jan 2014 10:11:11 GMT
Etag: "3e0dc0fda42ba657e7f8c7dea81f305b"
Server: nginx
X-Runtime: 71
Content-Length: 1513 
Connection: keep-alive

The actual request is like any other request, except for the mandatory CORS header. The X-Session-Token is included as a header field in the request. The server has given a green signal that it can be included during the pre-flight response. The response to the actual request is also very similar to the example in the last gist. It has the mandatory header Access-Control-Allow-Origin. The value of this header must match the request's Origin header.

A sad part about a CORS workflow with pre-flight requests is that an OPTIONS HTTP call is not allowed to be cached. So every CORS request that requires a pre-flight is 2 HTTP requests under the hood. The same behavior can be observed in Rapportive. Any subsequent call to fetch contact information again translates to 2 calls, the pre-flight and the actual call.

In Scibler, so far, we have not had a need for custom headers or any other CORS condition where the browser requests a pre-flight request. Our requests are mainly GET and we use cookie-based auth tokens.

[Here] (http://www.html5rocks.com/static/images/cors_server_flowchart.png) is a flowchart on how to do server programming for CORS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment