Friday, June 17, 2022

My notes on Cookies and CORS

Cookies

(This section assumes that you have a basic knowledge of what Cookies are. For proper introduction and more details about cookies, please read the Mozilla Developer Docs: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies. The flowcharts in this section are created by Nandan Desai)

Cookies are created on the client side when either the server responds with Set-Cookie header or when the cookies are set using Document.cookie in JavaScript.

And the browser sends the Cookies to the server on the next request using the Cookie header.

Understanding the intricacies of cookies boils down to understanding various cookie attributes.

Cookie attributes can be classified into two main categories:

  1. The attributes that restrict access to cookies (like Secure and HttpOnly)
  2. The attributes that define where the cookies are sent (like SameSite, Domain and Path)

'Secure' and 'HttpOnly'

Cookies that have Secure attribute set are only sent to the server on an encrypted (HTTPS) network. Also, sites with http:// in their URL can't set Secure attribute on cookies.

Cookies that have HttpOnly attribute set are not accessible via JavaScript and are sent to the server by the browser automatically when all the proper conditions are met.

'Domain', 'Path' and 'SameSite'

  • 'Domain' attribute

domain cookie attribute

  • 'Path' attribute

path cookie attribute

  • 'SameSite' attribute

Here, the "site" refers to the domain combined with the scheme (http or https). For example, http://example.com and https://example.com are different sites according to this definition.

samesite attribute

CORS (Cross-Origin Resource Sharing)

(Most of the CORS-related content presented here is either a direct copy-paste of Mozilla Developer Docs or I've made some minor modifications to make certain things simpler to understand. You can get more details on this topic here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)

In simple words, CORS is a communication between the server and the browser about what the browser is allowed to do when it receives some content from the server. It's like the server is doing Access Control on the browser.

Suppose web content at https://foo.example wishes to invoke content on domain https://bar.other. Code of this sort might be used in JavaScript deployed on foo.example:

const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';

xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();

This operation performs a simple exchange between the client and the server, using CORS headers to handle the privileges:



Let's look at what the browser will send to the server in this case:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
Origin: https://foo.example

The request header of note is Origin, which shows that the invocation is coming from https://foo.example.

Now let's see how the server responds:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[…XML Data…]

In response, the server returns a Access-Control-Allow-Origin header with Access-Control-Allow-Origin: *, which means that the resource can be accessed by any origin.

This pattern of the Origin and Access-Control-Allow-Origin headers is the simplest use of the access control protocol. If the resource owners at https://bar.other wished to restrict access to the resource to requests only from https://foo.example, (i.e no domain other than https://foo.example can access the resource in a cross-origin manner) they would send:

Access-Control-Allow-Origin: https://foo.example

CORS effect on Cookies

The most interesting capability exposed by both XMLHttpRequest and CORS is the ability to make "credentialed" requests that are aware of Cookies and HTTP Authentication information. By default, in cross-origin XMLHttpRequest invocations, browsers will not send credentials (i.e., cookies). A specific flag has to be set on the XMLHttpRequest object when it is invoked.

Consider the following example:

const invocation = new XMLHttpRequest();
const url = 'https://bar.other/resources/credentialed-content/';

function callOtherDomain() {
  if (invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send();
  }
}

If we want to send Cookies to the server then withCredentials = true needs to set on the XMLHttpRequest instance. And if the server responds with some cookie, then the server also needs to include Access-Control-Allow-Credentials: true header along with Access-Control-Allow-Origin not being a wildcard (i.e., it shouldn't be "*"). Only then, the response and the response cookies will be made available to the JavaScript code. Otherwise, a CORS error will be printed on the devtools console.

The following example explains it:



Here is a sample exchange between client and server:

GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
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
Connection: keep-alive
Referer: https://foo.example/examples/credential.html
Origin: https://foo.example
Cookie: pageAccess=2
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

[text/plain payload]

Although line 10 contains the Cookie destined for the content on https://bar.other, if bar.other did not respond with an Access-Control-Allow-Credentials: true (line 16), the response would be ignored and not be made available to the web content. Also notice the following: Access-Control-Allow-Origin: https://foo.example. If it was Access-Control-Allow-Origin: *, then browser would have not allowed the JavaScript to access the response or the response cookies as explained earlier.

References

https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies

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

No comments:

Post a Comment