Basics of CORS (Cross-Origin Resource Sharing)
Want to learn more about using CORS? Check out this post where we look at SOP and CORS and how to implement them on your project.
Join the DZone community and get the full member experience.
Join For FreeBefore learning more about CORS, it's important to familiarize yourself with SOP (Same-Origin Policy).
What Is SOP (Same-Origin Policy)?
SOP is a security mechanism implemented in almost all of the modern browsers. It does not allow documents or scripts loaded from one origin to access resources from other origins. Now, what is the origin? The origin is not only domain. — the origin could be the combination of the Host name, Port number, and URI (Uniform Resource Identifier) Scheme.
Why Is SOP Important?
For any HTTP request to a particular domain, browsers automatically attach any cookies bounded to that domain.
It doesn't matter if a request originates from "your-bank.com" or "malicous.com." As long as the request goes to your-bank.com, the cookies stored for your-bank.com would be used. As you can see, without the Same-Origin Policy, a Cross-Site Request Forgery (CSRF) attack can be relatively simple, assuming that authentication is based solely on a session cookie. That’s one of the reasons the SOP was introduced.
What Is CORS?
For security reasons, SOP is implemented in all latest browsers, and because of that, a website from one origin cannot access resources from a foreign origin, and to make that possible, CORS comes into the picture. In short, CORS is standard of sharing cross-origin resources. This allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.
- The client and server exchange a set of headers to specify behaviors regarding cross-domain requests.
Examples of Access Control Scenarios
Simple Request
The simple request is that the request that doesn't trigger a CORS preflight
The only allowed HTTP methods are: GET, HEAD, POST
The only allowded values for the 'Content-Type' header are:
1. application/x-www-form-urlencoded
2. multipart/form-data
3. text/plain
For example, suppose the web content on the domain http://a.com wishes to invoke content on the domain http://b.com. The code of this sort might be used within JavaScript deployed on http//a.com:
var invocation = new XMLHttpRequest();
var url = 'http://b.com/resources/public-data/';
function callOtherDomain() {
if(invocation) {
invocation.open('GET', url, true);
invocation.onreadystatechange = handler;
invocation.send();
}
}
Let us look at what the browser will send to the server in this case, and let's see how the server responds:
1. The request from "http://a.com" is sent to the other server, "http://b.com," with the following CORS-related headers:
GET /resources/public-data/ HTTP/1.1
Host: b.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://a.com/examples/access-control/simpleXSInvocation.html
Origin: http://a.com
2. The server "http://b.com" responds with the following CORS-related headers.
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
In this case, the server responds with an Access-Control-Allow-Origin: *
, which means that the resource can be accessed from any domain in a cross-site manner.
- Preflighted Request: Unlike "simple rquests" (discussed above), "preflighted requests" send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send.
The only allowed HTTP methods are: PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH.
The only allowed values for the 'Content-Type' header other than the below values are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
The following is an example of a request that will be preflighted.
var invocation = new XMLHttpRequest();
var url = 'http://b.com/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Vikas</name></person>';
function callOtherDomain(){
if(invocation)
{
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
invocation.setRequestHeader('Content-Type', 'application/xml');
invocation.onreadystatechange = handler;
invocation.send(body);
}
}
1. Preflighted Request:
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://a.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
2. Response to the preflighted request
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://a.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
3. Main Request: Once the preflight request is complete, the real request is sent:
POST /resources/post-here/ HTTP/1.1
Host: b.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://a.com/examples/preflightInvocation.html
Content-Length: 55
Origin: http://a.example
Pragma: no-cache
Cache-Control: no-cache
4. Response to the main request:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
- Java implementation of CORS Filter:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet Filter implementation class CORSFilter
*/
public class CORSSampleFilter implements Filter {
public CORSFilter() {
// TODO Auto-generated constructor stub
}
/**
* @see Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("CORSFilter HTTP Request: " + request.getMethod());
// Authorize (allow) all domains to consume the content
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Origin", "*");
((HttpServletResponse) servletResponse).addHeader("Access-Control-Allow-Methods","GET, OPTIONS, HEAD, PUT, POST");
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// For HTTP OPTIONS verb/method reply with ACCEPTED status code -- per CORS handshake
if (request.getMethod().equals("OPTIONS")) {
resp.setStatus(HttpServletResponse.SC_ACCEPTED);
return;
}
// pass the request along the filter chain
chain.doFilter(request, servletResponse);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
// TODO Auto-generated method stub
}
}
Register this filter in web.xml:
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.ksegvikas.examples.cors.CORSSampleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Conclusion
CORS (Cross-Origin Resource Sharing) is the standard way of sharing resources from one origin to another. As SOP (Same-Origin Policy) is for security purpose, which restricts sharing of resources from origin to another, CORS provides us with a standard way to access it with proper implementation, as shown in the above examples.
Opinions expressed by DZone contributors are their own.
Comments