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

Cross-origin Resource Sharing — A Hands-on Tutorial (Part II : Complex Requests)

msingh

--

Continuing from Part-I where we successfully handled a cross-origin “simple” request, lets see what non-simple requests are and what we can do to enable support for such requests.

Example #2 — Complex Requests

Starting from where we left off. We were running a PageServer serving HTML pages and an APIServer serving GetUser API call to return User JSON.

Lets see what happens when we now try to create a user using Javascript

  • Point your browser to createUser.html . You should see something like this
.Entering the data and clicking “create” sends a POST request to the ApiServer in-memory store

So, let’s add some string data in the form fields and click “submit” button. This should convert the data to JSON and do a POST to http://apiserver.cors.com:12346/users with the json data as the body of the request

fetch('http://apiserver.cors.com:12346/users', {
method: 'post',
body: JSON.stringify(user),
headers: {
"Content-type": "application/json"
},
}).....

However, what you get is this error

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://apiserver.cors.com:12346/users. (Reason: header ‘content-type’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).

What’s going on?

Pre-Flight

What we called as “complex” request actually causes two HTTP requests under the covers.

  • The browser first issues a preflight or an OPTIONS request, which is basically asking the server for permission to make the actual request.
  • Once permissions have been granted, the browser makes the actual request.

So, in this case, the pre-flight request is something like below

OPTIONS /users HTTP/1.1
Host: apiserver.cors.com:12346
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

Referer: http://pageserver.cors.com:12345/createUser.html
Origin: http://pageserver.cors.com:12345
Connection: keep-alive

The preflight request contains a few additional headers:

  • Access-Control-Request-Method — The HTTP method of the actual request.
  • Access-Control-Request-Headers — A comma-delimited list of non-simple headers that are included in the request. Notice that all CORS related headers are prefixed with “Access-Control-”.

In order for the POST to succeed, the server should support this request, “granting” permission based on the above request headers.

TIP: Unfortunately Chrome developer tool no longer shows the pre-flight call , see here https://stackoverflow.com/questions/57410051/chrome-not-showing-options-requests-in-network-tab . If you would like to see the OPTIONS request details, just use Firefox Debugger (or use chrome://net-internals)

Solution

In order for the POST to succeed, the server should support this request, “granting” permission based on the above request headers.

  • Stop apiserver/allow_origin/apiserver.go and Start preflight server

$ go run apiserver/preflight/apiserver.go

What we have done here is added some code in the apiserver to respond to OPTIONS request, granting the permission for GET, and POST calls with Content-Type header.

........
if (r.Method == "OPTIONS") && (reqMethod == "GET" || reqMethod == "POST") && (strings.EqualFold(reqHeader, "Content-Type"))
{
w.Header().Set("Access-Control-Allow-Methods", "POST, GET")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
return
}
.....
Full code here
  • Enter data and hit “create” button again. You will see that the request succeeded.
Success !

Debugger/Dev Tools will show that the response headers from Server granted the needed permission as response to OPTIONS request

HTTP/1.1 200 OK
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Origin: http://pageserver.cors.com:12345
Date: Mon, 01 Jun 2020 02:16:13 GMT
Content-Length: 0

In addition, the server can also return a header called Access-Control-Max-Age. The value of the header indicates how long the pre-flight response can be cached by the browser and hence browsers can skip the check for that duration

Hope this makes this a little bit easier when you are dealing with CORS failures . They can be really opaque (esp. now with Chrome dev tools not even showing pre-flight calls)

In the next part we will look at handling credentials in a CORS request

--

--