WARNING: this tutorial assumes you have a good working knowledge of HTTP. If you don't, read the HTTP tutorial first.
example.com be able to make HTTP requests to
api.example.com or even
Several creative hacks were developed to make this possible, the most popular being the JSONP technique. But these were always acknowledged as short-term hacks that needed to be replaced by a long-term solution. The great minds of the Web got together to figure out how to enable open API servers without compromising security. The result was the Cross-Origin Resource Sharing standard, more commonly referred to as CORS.
How CORS Works
Simple cross-origin requests are defined as follows:
- The method is GET, HEAD, or POST
- The request may contain only "simple" headers, such as
- If a
Content-Typeheader is included, it may only be one of the following:
application/x-www-form-urlencoded(format used when posting an HTML
multipart/form-data(format used when posting an HTML
text/plain(just plain text)
Origin header set to the current page's origin. The server may use this
Origin request header to determine where the request came from, and decide if it should process the request.
Originheader to ensure the request actually came from the local CLI, and not a web page loaded from the Internet. Thankfully, some security researchers discovered this, and Docker fixed the vulnerability. The moral of the story is, if your server supports simple POST requests that are not meant to be called cross-origin, you must check the
Originheader and reject the request if it's not set to what you expect.
If the server does process the request and responds with a 200 (OK) status code, it must also include a response header named
Access-Control-Allow-Origin set to the value in the
Origin request header, or
Supporting simple cross-origin requests on the server-side is therefore as simple as adding one header to your response:
Access-Control-Allow-Origin: *. If you want to restrict access to only a small set of white-listed origins, you can compare the
Origin request header against that list and respond accordingly.
OPTIONS HTTP method instead of the actual request method. The browser also adds the following headers to the preflight request:
Originset to the origin of the current page.
When the server receives the preflight request, it can examine these headers to determine if the actual request should be allowed. If so, the server should respond with a 200 (OK) status code, and include the following response headers:
Access-Control-Allow-Originset to the value of the
Originrequest header, or
*(same as in simple request).
Access-Control-Allow-Methodsset to a comma-delimited list of HTTP methods the server will allow on the requested resource.
Access-Control-Allow-Headersset to a comma-delimited list of non-simple headers the server will allow in a request for the resource.
trueif the server will allow the browser to send cookies during the actual request. If omitted or set to
false, the browser will not include cookies in the actual request.
All of the following must be true for the browser to then send the actual request to the server:
Access-Control-Allow-Originresponse header matches
*or the value in the
- The actual request method is found in the
- The non-simple request headers are all found in the
If any of these are not true, the browser doesn't send the actual request and instead generates an HTTP error.
CORS and CSRF Attacks
.withCredentials property on the
XMLHttpRequest object, or sets the
credentials property in the options passed to the new
fetch() API, the browser will send all stored cookies for the target origin with the request, with no warning shown to the user.
Set-Cookie response header, and provided in requests using the
Cookie header, and both of these are automatically handled by the browser. If you use some other header that is not automatically handled, such as
CORS Middleware in Go
If you are building a web server that exposes many APIs, and you want many or all of them to be callable from any origin, you will quickly notice that managing all of these headers requires a good bit of code that you don't want to repeat in every handler function. Thankfully we can use a technique called "middleware" to handle this logic in one place. For more details, see my Middleware in Go tutorial.