Cross-Origin Resource Sharing

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

Web browsers are built so they don't allow JavaScript that runs as part of web page to make HTTP requests via the XMLHttpRequest interface to other sites than the origin web site. In some cases - Chrome, for example, the HTTP request is made, but the browser will just throw out the response and log a JavaScript error:

XMLHttpRequest cannot load http://b.com/B.html. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://a.com' is therefore not allowed access.

The same-origin policy only applies to web fonts and AJAX (XMLHttpRequest) requests. A web page may freely embed images, stylesheets, scripts, iframes, videos and some plugin content (such as Adobe Flash) from any other domain.

A cross-origin HTTP request is one that is made to:

  • A different domain.
  • A different subdomain.
  • A different port.
  • A different protocol.
CORS.png

Why?

The web browser wants to prevent the situation when an user unknowingly downloads malicious behavior from a web site A, which then starts to make invocations into other site B. In order for this to be allowed, site B has to explicitly allow it - and then the browser allows it too. The browser insures this by essentially asking B: "Do you allow scripts downloaded from A to invoke into you?" This invocation is called "pre-flight".

Cross-Origin Resource Sharing

Cross-origin calls can be made with the cooperation of the second server. The standard way to do it is to employ a protocol called Cross-Origin Resource Sharing (CORS, https://fetch.spec.whatwg.org/#http-cors-protocol). The protocol consists of a simple header exchange between the client and the second server. This standard extends HTTP with a new Origin request header and a new Access-Control-Allow-Origin response header. Web browsers expect Access-Control-Allow-Headers, and Access-Control-Allow-Origin headers to be set up in each method that accepts CORS requests. In addition, as described below, some browsers first make an HTTP request to an OPTIONS method in the same resource, and then expect to receive the same headers: A browser rendering content from http://A.com wants to send a request into http://B.com so it “pre-flights” the request by inquiring the B.com server on its capabilities with an Origin header sent over an OPTIONS request:

OPTIONS / HTTP/1.1
Host: B.com
Access-Control-Request-Method: GET
Origin: http://A.com
Referer: http://A.com/the-page-containinig-js.html

If B.com wants to allow cross-origin calls, it should respond with a pair of headers:

Access-Control-Allow-Origin: http://A.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE

To allow general access, the second server could respond with:

Access-Control-Allow-Origin: *

Origin

The origin is defined by {protol, host, port}. The algorithm to calculate the origin of a URI is specified in RFC 6454 https://tools.ietf.org/html/rfc6454/

Headers

Access-Control-Allow-Origin

Access-Control-Allow-Origin: <origin> | *

The header specifies either a single origin, which tells the browser to allow that origin to access the resource, or, for requests without credentials, the "*" wildcard, which tells the browser to allow any origin to access the resource. If the server specifies a single origin rather than the "*" wildcard, then the server should also include "Origin" in the "Vary" response header, to indicate that server responses will differ based on the value of the "Origin" request header.

Access-Control-Allow-Methods

Access-Control-Allow-Headers

The Access-Control-Allow-Headers header is used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

Access-Control-Max-Age

Access-Control-Allow-Credentials

Access-Control-Expose-Headers

This header allows a server to whitelist headers that browsers are allowed to access:

Access-Control-Expose-Headers: <field-name>[, <field-name>]*

Behavior in Presence of Custom Headers

If the cross call contains custom headers, the browser includes those custom headers in the pre-flight invocation:

OPTIONS /blah HTTP/1.1
Host: B.com
Access-Control-Request-Method: GET
Origin: http://A.com
Access-Control-Request-Headers: onecloud-adgroup, onecloud-user
Referer: http://A.com/the-page-containinig-js.html

Note the presence of "Access-Control-Request-Headers" in the request.

B.com must acknowledge the headers, otherwise we get an error message similar to:

XMLHttpRequest cannot load http://B.com/blah/. Request header field OneCloud-User is not allowed by Access-Control-Allow-Headers in preflight response.

Acknowledgement is provided by returning an "Access-Control-Allow-Headers" header listing the comma-separated list of allowed headers:

Access-Control-Allow-Headers: Apples, Oranges

Cross-Site Request Forgery (CSRF)

Cookie-to-Header Token.

Web applications that use JavaScript for the majority of their operations may use an anti-CSRF technique that relies on same-origin policy:

1. On login, the web application sets a cookie containing a random token that remains the same for the whole user session

Set-Cookie: Csrf-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/

2. JavaScript operating on the client side reads its value and copies it into a custom HTTP header sent with each transactional request

X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql

The server validates presence and integrity of the token

Cross-Site Request Forgery and Spring

Cross-Site Request Forgery and Spring

Testing CORS

test-cors.org

https://www.test-cors.org

The https://www.test-cors.org page has a "Code" tab that shows the JavaScript code used to send the request. It can be used as a starting point for a more complex CORS test. Also see Testing CORS with a JavaScript Client below.

JavaScript Client to Test CORS

JavaScript Client to Test CORS

CORS Support in Spring

CORS is supported in Spring with @CrossOrigin.

TODO

See how swagger-generated code implements in io.swagger.api.ApiOriginFilter:

package io.swagger.api;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

@javax.annotation.Generated(value = "class io.swagger.codegen.languages.JaxRSServerCodegen", date = "2015-09-18T16:59:22.303Z")
public class ApiOriginFilter implements javax.servlet.Filter {
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		HttpServletResponse res = (HttpServletResponse) response;
		res.addHeader("Access-Control-Allow-Origin", "*");
		res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
		res.addHeader("Access-Control-Allow-Headers", "Content-Type");
		chain.doFilter(request, response);
	}

	public void destroy() {}

	public void init(FilterConfig filterConfig) throws ServletException {}
}