Understanding and using CORS

Cross Origin Resource Sharing (CORS) allows to use Web applications within browsers when domains arent the same. For example, a site with domain test.org wants to execute AJAX requests to a Web application with domain domain mydomain.org using HTTP.

Understanding CORS

With CORS, the remote Web application (here the one with domain mydomain.org) chooses if the request can be served. The CORS specification distinguish two distinct use cases:

  • Simple requests. We are in this case if we use HTTP methods GET, HEAD and POST. In the case of POST method, only content types with following values are supported: text/plain, application/x-www-form-urlencoded, multipart/form-data.
  • Preflighted requests. When you arent in the case of simple requests, a first request (with HTTP method OPTIONS) is done to check what can be done in the context of cross-domain requests.

Client and server exchange a set of headers to specify behaviors regarding cross-domains requests. Lets have a look at them now. We will describe then how they are used for the two use cases.

  • Origin: this header is used by the client to specify from which domain the request is executed. The server uses this hint to authorize or not the cross-domain request.
  • Access-Control-Request-Method: in the context of preflighted requests, the OPTIONS request sends this header to check if the target method is allowed in the context of cross-domain requests.
  • Access-Control-Request-Headers: in the context of preflighted requests, the OPTIONS request sends this header to check if headers are allowed for the target method in the context of cross-domain requests.
  • Access-Control-Allow-Credentials: this specifies if credentials are supported for cross-domain requests.
  • Access-Control-Allow-Methods: the server uses this request to tell which headers are authorized in the context of the request. This is typically used in the context of preflighted requests.
  • Access-Control-Allow-Origin: the server uses this request to tell which domains are authorized for the request.
  • Access-Control-Allow-Headers: the server uses this request to tell which headers are authorized in the context of the request. This is typically used in the context of preflighted requests.

In the case of simple requests, the request is executed against the other domain. If the remote resource supports cross domains, the response is directly sent back. Otherwise an error occurs.

Here is a sample content for a cross-domain request:

GET /myresource/ HTTP/1.1
Host: mydomain.org
(...)
Referer: http://test.org/example.html
Origin: http://test.org

And the corresponding response:

HTTP/1.1 200 OK
(...)
Access-Control-Allow-Origin: *
Content-Type: application/json

[JSON Data]

In the case of preflighted requests, this corresponds to a negociation between the caller and the Web application based on HTTP headers. It consists in two phasis:

  • The browser first executes an OPTIONS request with the same URL than the target request to check if it has the rights to execute the request. This OPTIONS request returns headers that identifies what is possible to do for the URL.
  • If rights match, the browser actually execute the request.

Here is a sample content for the first a cross-domain request (the OPTIONS one):

GET /myresource/ HTTP/1.1
Host: mydomain.org
(...)
Origin: http://test.org
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-MYHEADER

And the corresponding response:

HTTP/1.1 200 OK
(...)
Access-Control-Allow-Origin: http://test.org
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-MYHEADER
Access-Control-Max-Age: 1728000

Since the pre request OPTIONS succeeds, the browser will then send the actual request:

POST /myresource/ HTTP/1.1
Host: mydomain.org
Content-type: application/json
Accept: application/json
(...)
Referer: http://test.org/example.html
Origin: http://test.org

[JSON Data]

And the corresponding response:

HTTP/1.1 200 OK
(...)
Access-Control-Allow-Origin: *
Content-Type: application/json

[JSON Data]

This processing is completely transparent for the caller but we can have hints of what actually happens using the development tools of the brower, for example Firebug within Firefox. With the later, we can use the Network tab to check which calls are actually executed and which CORS headers are exchanged.

Implement CORS within Restlet

Implementing CORS within a Restlet application corresponds to implement dedicated filter that manages the CORS headers. This filter is then configured within the routing for request.

We can see below the content of the CORS filter. It handles both simple and preflighted requests. When an OPTIONS request for cross domains is received, the filter returns right after having set the headers for cross domains.

Filter filter = new Filter(getContext(), next) {
    @SuppressWarnings("unchecked")
    @Override
    protected int beforeHandle(Request request, Response response) {
        // Initialize response headers

        Series<Header> responseHeaders = (Series<Header>) response
                    .getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
        if (responseHeaders == null) {
            responseHeaders = new Series<Header>(Header.class);
        }

        // Request headers

        Series<Header> requestHeaders = (Series<Header>) request
                     .getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
        String requestOrigin = reqHeaders.getFirstValue("Origin",
            false, "*");
        String rh = reqHeaders.getFirstValue(
            "Access-Control-Request-Headers", false, "*");

        // Set CORS headers in response

        responseHeaders.set(
            "Access-Control-Expose-Headers",
            "Authorization, Link");
        responseHeaders.set("Access-Control-Allow-Credentials", "true");
        responseHeaders.set("Access-Control-Allow-Methods",
            "GET,POST,PUT,DELETE");
        responseHeaders.set("Access-Control-Allow-Origin", requestOrigin);
        responseHeaders.set("Access-Control-Allow-Headers", rh);

        // Set response headers

        response.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS,
            responseHeaders);

        // Handle HTTP methods

        if (org.restlet.data.Method.OPTIONS.equals(request.getMethod())) {
            return Filter.STOP;
        }
        return super.beforeHandle(request, response);
    }
};
return filter;

The filter first gets the headers regarding CORS in the request. The Origin one gives the domain of the caller. We can base on it to authorize or not the request. The second one, Access-Control-Request-Headers, corresponds to the headers that the caller wants to use. Like for the first CORS headers, we can base on it to authorize or not the call.

The previous filter needs now to configure in the routing of your Restlet application. With Restlet, this corresponds to an execution chain. We return the filter instance within the method createInboundRootof the Restlet application and define its elements as next element of this filter. Like that, all requests against the application will first pass within the filter to handle CORS. Following code describe how to configure the CORS filter within a simple Restlet application:

private Filter createCorsFilter(Restlet next) {
    Filter filter = new Filter(getContext(), next) {
        (...)
    };
    return filter;
}

public Restlet createInboundRoot() {
    Router router = new Router(getContext());
    router.attach("/app/myresource", MyResource.class);
    (...)

    return createCorsFilter(router);
}

Conclusion

As you can see, using CORS isnt so tricky but the errors triggered by the technology can be weird and not intuitive at the browser level. We need to be carefull of the CORS headers and corresponding values sent back when implementing server resources that support cross-domain request.

This entry was posted in Cors, Restlet and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s