# CORS Cross Origin Resource Sharing is a way to protect a user's browser session from being exploited by rogue js to make calls on their behalf to other services. For example, if I had logged into my bank at bank.com my browser would be allowed to make requests to the bank's api to receive information, make transactions etc. This is all allowed operations while on bank.com, but we need to be secured from the same calls made from google.com. If a rogue js had inflitrated google.com and CORS was not in place, it could make requests to bank.com which would appear valid because I had logged in and a session cookie existed in my browser. Information would be stolen, transactions made, all against my will. With CORS in place, thoses requests can still issue and the responses return, but the browser will prevent the js from accessing the response. All requests by default must match: domain, protocol, and port. There are two types of requests: simple and preflight. A simple request is your standard `GET`, `POST` etc with only simple `Content-type` values of `application/x-ww-form-urlencoded`, `multipart/form-data`, `text-plain`. Requests that send more information such as cookies (and therefore are of a different `Content-type`) a **preflight** mechanism kicks in where an `OPTIONS` request is made before the official request to check for CORS access before sending the simple request. ## Client Side ### Origin This header comes in the request from the client and contains the domain of the issuer. It is set by the browser and cannot be overwritten for security reasons. ### Access-Control-Request-Method Sent in preflight requests to check if the server supports the HTTP method. `Access-Control-Request-Method: PUT` ### Access-Control-Request-Headers Sent in a preflight request to let the server know what headers it could expect in the followup request: `Access-Control-Request-Headers: [, ]*` ## Server Side ### Access-Control-Allow-Origin `Access-Control-Allow-Origin` is a header sent back from the server in the response. The value can be `*` to allow any domain to access or a fully qualified domain name `https://www.example.com` to limit the access. In the case that you require the client to send cookies or other Authentication headers, the `Access-Control-Allow-Origin: *` response will not be supported. ### Access-Control-Allow-Headers Used in response to preflight `OPTIONS` call. A comma separated list of request header values the server supports. Useful if you also use custom headers. ### Access-Control-Expose-Headers Used in response to preflight `OPTIONS` call. A comma separated list of headers allowed to be exposed to the client on response. ### Access-Control-Allow-Methods Used in response to preflight `OPTIONS` call. A comma separated list of HTTP request type such as `GET`, `POST` which the server supports. ### Access-Control-Allow-Credentials `Access-Control-Allow-Credentials` is required if credentials (cookies) are sent in the request. It must be `true` or `false` as a value. ## Dealing with Cookies / Authentication By default browsers will not send cookies and authentication headers to the server in a request. In Javascript we can tell the `XMLHttpRequest` object to include that credentialed information with `withCredentials = true`. The request could still be a simple `GET` and no preflight check sent first, but all responses that do not contain `Access-Control-Allow-Credentials: true` will be isolated by the browser. In addition the server must also respond with `Access-Control-Allow-Origin` and a specific origin and not `*`. ## Securing CORS in Server Side Ultimately the CORS security can be bypassed with proxy servers and non-browser requests, so it is primarily a browser security element to protect end users who interact with the server through browser scripts. So CORS is not a reliable to limit your server from unwanted requests, but it is a way to protect consumers from being exploited. It will fail if: - Client sends simple `GET`/`POST` and Server does not respond with `Access-Control-Allow-Origin` matching or wildcard `*` - Client sends simple `GET`/`POST` with credentials and Server does not responde with `Access-Control-Allow-Origin` as an exact match AND `Access-Control-Allow-Credentials: true` - Client sends a `PUT`/`DELETE` and Server does not respond with `Access-Control-Allow-Origin` matching or wildcard `*` AND `Access-Control-Allow-Methods` does not include the request method - Client sends a `PUT`/`DELETE` with credentials and Server does not respond with `Access-Control-Allow-Origin` matching or wildcard `*` AND `Access-Control-Allow-Methods` does not include the request method AND `Access-Control-Allow-Credentials` is not `true` Here is some testing scenarios and how the CORS security reacted: ### Simple Requests / No Credentials Client: `cors-client.jordansavant.com` Server: `cors-server.jordansavant.com` ``` javascript var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4 document.getElementById("response").innerHTML = xmlhttp.responseText || 'fail'; document.getElementById("status").innerHTML = xmlhttp.status || 'fail'; } }; xmlhttp.open("GET", "http://cors-server.jordansavant.com", true); xmlhttp.send(); ``` A `GET` request with headers is sent that looks like: ``` text Accept */* Accept-Encoding gzip, deflate Accept-Language en-US,en;q=0.5 Connection keep-alive DNT 1 Host cors-server.jordansavant.com Origin http://cors-client.jordansavant.com Referer http://cors-client.jordansavant.com/ User-Agent Mozilla/5.0 (Windows NT 10.0; ...) Gecko/20100101 Firefox/58.0 ``` - `Access-Control-Allow-Origin` unsent: > "Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)." - `Access-Control-Allow-Origin: *` > Successful - `Access-Control-Allow-Origin: http://praetor64.com` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘http://praetor64.com’). - `Access-Control-Allow-Origin: http://cors-server.jordansavant.com` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘http://cors-server.jordansavant.com’). - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` > Successful - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com/foo` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘http://cors-client.jordansavant.com/foo’). - `Access-Control-Allow-Origin: http://cors-client.Jordansavant.com` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘http://cors-client.Jordansavant.com’). ### Simple Requests / With Credentials Existing Cookies: - There is a cookie underneath `cors-client.jordansavant.com` that is `client=abc` - There is a cookie underneath `.jordansavant.com` that is `root=def` - The server set a cookie on each request `cors-server.jordansavant.com` that is `server=` ``` javascript var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4 document.getElementById("response").innerHTML = xmlhttp.responseText || 'fail'; document.getElementById("status").innerHTML = xmlhttp.status || 'fail'; } }; xmlhttp.open("GET", "http://cors-server.jordansavant.com", true); xmlhttp.withCredentials = true; xmlhttp.send(); ``` A `GET` request with headers is sent that looks like: ``` Accept */* Accept-Encoding gzip, deflate Accept-Language en-US,en;q=0.5 Connection keep-alive Cookie _root=def; server=hij DNT 1 Host cors-server.jordansavant.com Origin http://cors-client.jordansavant.com Referer http://cors-client.jordansavant.com/ User-Agent Mozilla/5.0 (Windows NT 10.0; ...) Gecko/20100101 Firefox/58.0 ``` - `Access-Control-Allow-Origin: *` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘http://cors-server.jordansavant.com/?r=ncreds-all’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’). - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/?r=creds. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’). - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` and `Access-Control-Allow-Credentials: true` > Successful > > Visible Server Cookies: `root` and `server` The cookies stored underneath the client, however, are not sent / visible. ### Preflight Requests / No Credentials ``` javascript var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4 document.getElementById("response").innerHTML = xmlhttp.responseText || 'fail'; document.getElementById("status").innerHTML = xmlhttp.status || 'fail'; } }; xmlhttp.open("PUT", "http://cors-server.jordansavant.com", true); xmlhttp.withCredentials = true; xmlhttp.send(); ``` A preflight `OPTIONS` request is made that looks like: ``` text Accept text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8 Accept-Encoding zip, deflate Accept-Language en-US,en;q=0.5 Access-Control-Request-Method PUT Connection keep-alive DNT 1 Host cors-server.jordansavant.com Origin http://cors-client.jordansavant.com User-Agent Mozilla/5.0 (Windows NT 10.0; ...) Gecko/20100101 Firefox/58.0 ``` And on success a `PUT` request is made that looks like: ``` text Accept */* Accept-Encoding gzip, deflate Accept-Language en-US,en;q=0.5 Connection keep-alive Content-Length 0 DNT 1 Host cors-server.jordansavant.com Origin http://cors-client.jordansavant.com Referer http://cors-client.jordansavant.com/ User-Agent Mozilla/5.0 (Windows NT 10.0; ...) Gecko/20100101 Firefox/58.0 ``` - `Access-Control-Allow-Origin` unsent: > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/?r=put-none. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). - `Access-Control-Allow-Origin: *` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/?r=put-all. (Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’). - `Access-Control-Allow-Origin: *` and `Access-Control-Allow-Methods: GET, PUT, POST` > Successful - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/?r=put-match. (Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’). - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` and `Access-Control-Allow-Methods: GET, PUT, POST` > Successful In the final scenario the request is allowed because the Origin explicitly matches and the `PUT` is in the allowed methods. ### Preflight Requests / With Credentials - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` and `Access-Control-Allow-Methods: GET, PUT, POST` > Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cors-server.jordansavant.com/?r=put-match-allow-nocreds. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’). - `Access-Control-Allow-Origin: http://cors-client.jordansavant.com` and `Access-Control-Allow-Methods: GET, PUT, POST` and `Access-Control-Allow-Credentials: true` > Successful ## Spoofing Proof with CURL The above scenarios show how to configure the browser client code and the server side response code to work through CORS security protocols. But we must realize that the CORS security measures are browser specific and do not enforce and level of security outside of that. Here are some examples of the same calls being made with CURL and how the response is always recieved no matter what. ``` text $ curl -X PUT -H "Cookie: root=abc;server=123" http://cors-server.jordansavant.com?r=put-match-allow-creds -v ``` ``` text > PUT /?r=put-match-allow-creds HTTP/1.1 > Host: cors-server.jordansavant.com > User-Agent: curl/7.47.0 > Accept: */* > Cookie: root=abc;server=123 > < HTTP/1.1 200 OK < Date: Sun, 11 Mar 2018 15:32:43 GMT < Server: Apache/2.4.18 (Ubuntu) < Access-Control-Allow-Origin: http://cors-client.jordansavant.com < Access-Control-Allow-Methods: GET, PUT, POST < Access-Control-Allow-Credentials: true < Content-Length: 176 < Content-Type: application/json < { "response": true, "method": "PUT", "request": { "r": "put-match-allow-creds" }, "cookie": { "root": "abc", "server": "123" } } ``` So if a simple CURL request can spoof the Origin and Cookie headers, then how is CORS a security measure at all? If our server set cookies as a way to identify the session of a user in the browser, it would set them under the server's domain. Any rogue js loaded into the browser from another domain would only have access to the domain of the presentation page and not cookies set in the domain of the server. This is a browser security restriction to prevent session hijacking through XSS attacks. Given this, even though a person could spoof the id being sent through the Cookie header, they cannot actually steal a known cookie and make those same requests. ### When to Use This If you design an API that will be using user authentication or session identification to send requests back and forth, it is an important security layer. By using Cookies to identify a user, the browser will secure it from rogue js taking it as it will be stored under your API's domain and not under the domain of the website that makes the request. Therefore it is important that any public endpoints that manipulate sensitive user information be secured with cookie identification.