Image courtesy : https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Cross-origin Resource Sharing — A Hands-on Tutorial

msingh

--

Richer and interactive web pages today are built using dynamic client side scripting. It’s extremely common to have the javascript in the page interact with different web APIs transparently, to provide a smooth experience to the user. But when the script in your page needs to interact with APIs that belong to a “different” origins then it comes in conflict with browser’s same origin policy.

This article aims to provide a complete hands-on tutorial to help you understand CORS in detail, as well as what is needed to support Cross Origin request by Servers

Pre-Requisites

To try live demos as you follow along this article, I would highly recommend to download associated source code for servers and clients here. You can run the servers locally by following the instructions in the README

Cross-Origin Resource Sharing (CORS)

is a W3C spec that allows cross-domain communication from the browser. CORS is becoming increasingly more important as we use multiple API’s and services to create a mashup/stitched user experience

To understand cross origin resource sharing, first we need to understand the concept of an “origin”.

What is an Origin?

Two pages have the same origin if the protocol, port (if one is specified), and host are the same for both pages. So

has same origin as

but different from

There are some exceptions to the above rule (mostly by IE !) but they are non-standard.

Same Origin Policy

By default, Browsers enforce Same Origin Policy for HTTP requests initiated from within scripts. A web application using XMLHttpRequest could only make HTTP requests to its own domain.

TIP: Cross origin embedding is allowed. Browsers can load scripts(source),images, media files embedded within the page even if they are from a different origin.

In this blog we will focus on the main restriction, cross origin requests using XMLHttpRequest

Enter CORS

The Cross-Origin Resource Sharing standard works by adding new HTTP headers that allow servers to describe the set of origins that are permitted to read that information using a web browser.

TIP: It’s the Servers which are in control, not the client.

Experiment #1 — A Simple Request

So let’s first see what happens when we do a cross origin XMLHttpRequest.

For these examples, we will be running two servers —

  1. API Server serving API requests
  2. A Page Server serving pages. The pages are interactive and will make ajax calls to APIServer based on user actions

Page Server

  • This server runs on a (default) port 12345 and serves HTML files for the demo. Here is the relevant code :
func fileHandler(w http.ResponseWriter, r * http.Request) {
fmt.Printf("Requested URL %v\n", r.URL.Path)
http.ServeFile(w, r, r.URL.Path[1: ])
}
See the full code
  • Start the page server
$ cd pageserver
$ go run pageserver.go

TIP: If you are seeing error “listen tcp 127.0.0.2:12345: bind: can’t assign requested address” on Mac, Run this command — sudo ifconfig lo0 alias 127.0.0.2 up to enable the loopback address.

API Servers

  • These are the different servers which expose a basic User REST API
  • API returns a JSON representing a User object based on the user name in the request URL.
func userHandler(w http.ResponseWriter, r * http.Request) {
w.Header().Set("Content-Type", "application/json")
b, _: = json.Marshal(userData[r.URL.Path[len("/users/"): ]])
io.WriteString(w, string(b))
}
Full Code
  • The User is just a simple struct saved in an in-memory map.
type User struct {
UserName string
FirstName string
LastName string
Country string
}
  • Run the simple apiserver
    $ go run apiserver/basic/apiserver.go

Open the browser and load the html http://pageserver.cors.com:12345/showuser.html

Here is how this looks

If you enter “john” (we start with one hardcoded user ) and click “show”, it is supposed to go to http://apiserver.cors.com12346/users/john and get the user json to display but instead you see this error in console :

Access to fetch at ‘http://apiserver.cors.com:12346/users/john' from origin ‘http://pageserver.cors.com:12345' has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

This is called a “Simple” Cross origin GET request

Simple requests are requests that meet the following criteria —

HTTP Method matches one of HEAD, GET or POST and HTTP Headers matches one or more of these

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type — but only if the value is one of: application/x-www-form-urlencoded, multipart/form-data, text/plain

An easy rule of thumb is that if you could do the same request without using Javascript then its a Simple Request (say using a FORM POST or HREF)

Lets see what we can do to succeed in serving a Simple cross origin request

First stop the simple apiserver and start the “allow origin” server

$ go run apiserver/allow_origin/apiserver.go

  • What we have done in this server is added the Access-Control-Allow-Origin header for any incoming GET request.
  • The value of the header is same as the value sent by browser for the Origin header in the request.
  • This is equivalent to allowing requests that come from any origin (*)
func corsWrapper(fn func(http.ResponseWriter, * http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r * http.Request) {
origin: = r.Header.Get("Origin")
fmt.Printf("Request Origin header %s\n", origin)
if origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}

fn(w, r)
}
}
func main() {
...
http.HandleFunc("/users/", corsWrapper(userHandler))
...
}

The code here is essentially creating a wrapper http handler function which sets the header and then executes the wrapped function (userHandler()). See full code for apiserver_allow_origin.go

Lets attempt clicking the “show” button again and voila we see the data returned by the server !

Its all good until we realize that just adding Access-Control-Allow-Origin isn’t sufficient for certain “complex” requests (or anything which isn’t covered in the Simple request).

In the next part of this tutorial, we will discuss more about how to handle “complex” requests.

--

--