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

Cross-origin Resource Sharing — A Hands-on Tutorial (Part III : Cookies)

msingh
5 min readJun 5, 2020

--

Part II of the tutorial dealt with complex CORS requests and pre-flight check by the browsers. In this final part, we look at dealing with cookies in CORS . We will also look at subtle differences between same site and same origin and how it impacts cookie behaviour.

Cookies

By default, Cookies are neither set nor sent in CORS Requests. Let’s see that in a bit more detail.

The Cookie Demo Page

  • Its a simple HTML page served by the PageServer, which allows you to play with different scenarios
  • What we are going to do is first make a request to APIServer for user “john” and expect the server to set a cookie “visited-userid=john”
  • The next request to the APIServer will make a Fetch call with /@me and it expects the APIServer to read the visited-userid cookie from the request .
  • If successful, we should see “john” JSON data displayed on the page . If the server doesn’t find the cookie in the incoming request it will simply return {"error":"user not found"} with 404 status code
Run the page and API Servers
$ go run pageserver.go
and in different window
$ go run apiserver/allow_creds/apiserver.go
fetch(url, {
credentials: 'include',
....}

Observations

  1. Here is what you would see in debugger/dev tools

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://apiserver.cors.com:12346/users/john. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).

Security Risk ⚠️ Even though the response was blocked from rendering, the request indeed made it to the Server and was processed. You can confirm this by looking at the stdout of ApiServer as well as dev tools Network call.

Lets fix it —

  • Stop the earlier running ApiServer and now run it in “allow” mode
$ go run apiserver/allow_creds/apiserver.go --allow-creds
  • Hit the Set Cookie via Ajax button again and you would see a response now, the JSON data for user John {“UserName”:”jdoe”,”FirstName”:”John”,”LastName”:”Doe”,”Country”:”France”}
  • The only change in Server was adding Access-Control-Allow-Credentials header when allow-creds param is set.
if *allowCreds {
w.Header().Set("Access-Control-Allow-Credentials", "true")
}

Response Headers -

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://pageserver.cors.com:12345
Content-Type: application/json
Set-Cookie: visited-userid=john; SameSite=Strict
Date: Fri, 05 Jun 2020 07:32:26 GMT
Content-Length: 74

Now let’s do the second request by hitting the Use Cookie Set Earlier button. This next request to the APIServer makes a Fetch call with /@me and expects the APIServer to read the visited-userid cookie from the request and return “john” data with 200 OK status . This works as well ☀️

So we have successfully set a cookie, as well as read it from the incoming request. On to another interesting/confusing aspect

Same- Site is != Same-Origin

Readers paying close attention may have noticed that theSet-Cookie header returned by the APIServer had an attribute SameSite=Strict.

If you set SameSite to Strict, your cookie will only be sent in a first-party context. I.e. cookie will only be sent if the site for the cookie matches the site currently shown in the browser's URL bar.

The URL of the page is http://pageserver.cors.com:12345/cookiedemo.html where as the request was sent to http://apiserver.cors.com:12346/users/john

So why was the cookie sent?

Well its because they are cross-origin but same-site 🙀 . The same or cross site distinction is based on “effective TLD” . Or in user terms, pageserver.cors.com and apiserver.cors.com have same value i.e. cors.com .

🔗 Checkout this page to understand the rules

You can see this in action by making a couple of changes

  • Create a cross-site server. Bind a new loopback IP to apiserver.sscors.com on your machine.
For example this entry in my /etc/hosts file.127.0.0.4 apiserver.sscors.com

Notice that the value for server issscors.com which is different from cors.com (different values for eTLD+1)

  • Run the API Server in cross-site mode (add --cross-site command line param)
$ go run apiserver/allow_creds/apiserver.go --allow-cred --cross-site

On the cookiedemo.html page, go to the Cross Site demo section and hit the “Set Cookie Via Ajax Fetch” button. The request succeeds and indeed the response has Set-Cookie header

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://pageserver.cors.com:12345
Content-Type: application/json
Set-Cookie: visited-userid=john; SameSite=Strict
Date: Fri, 05 Jun 2020 08:28:34 GMT
Content-Length: 74

Now lets hit the Use Cookie Set Earlier button but this returns error !

{"error": "user not found"}

Why?

A look at the request dump in server or browser dev tools tells the story

GET /users/@me HTTP/1.1
Host: apiserver.sscors.com:12346
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.5
Cache-Control: no-cache
Connection: keep-alive
Origin: http://pageserver.cors.com:12345
Pragma: no-cache
Referer: http://pageserver.cors.com:12345/cookiedemo.html
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0

🔎 Notice that -

  1. The visited-userid cookie was not included in the request since the SameSite=Strict attribute was set on the cookie.
  2. There was no issue in setting the cookie but it won’t be included in the cross-site GET or POST request with SameSite as Strict
  3. The server still has Access-Control-Allow-Credentials: true for cross-origin requests, but its overridden by same-site cookie setting

Can we “fix” it?

While its generally a bad idea to allow cross-site cookies, if you did want to enable that, change the cookie attribute toSameSite=None .

  • Run the API Server to set same site as none (add --same-site-none command line param)
$ go run apiserver/allow_creds/apiserver.go --allow-creds --cross-site --same-site-none

Now make the first request to set the cookie again. The response header from server shows the cookie attribute as none

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://pageserver.cors.com:12345
Content-Type: application/json
Set-Cookie: visited-userid=john; SameSite=None
Date: Fri, 05 Jun 2020 08:46:12 GMT
Content-Length: 74

  • Now hitting the button, Use Cookie Set Earlier , would show john’s data

🔥SameSite=None opens the door for CSRF and other vulnerabilities and you should consider its use carefully .

⚠️This example above may not work in future since browser can potentially discard this setting on an HTTP connection . e.g. warning in firefox — Cookie “visited-userid” will be soon rejected because it has the “sameSite” attribute set to “none” or an invalid value, without the “secure” attribute.

--

--