The effect of request and response headers and the purpose of CORS.
Style: guided
Overview
- Before starting this workshop, check you have the requisite tools and setup.
- By the end of this workshop, you will be able to:
- Set request and response headers
- Explain CORS from the perspective of front-end web development.
Exercises
A set of exercises to test your understanding of http request and response headers.
-
Same-origin policy in the browser
Browser: start the http server and open a new web browser. Enter this request in the address bar:
http://localhost:3000?colour=red
The response is returned in the browser (and logged to the console).
The response can be accessed because the requesting origin is the same as the resource origin.
The origin is composed of scheme/domain/port. In this case, http/localhost/3000.
-
Unsuccessful request
Browser: start the http client and open it in the browser.
The same-origin policy (SOP) prevents requests from a web Browser (one origin) accessing the response to a request sent to a different origin.
Start up the http client and send a request to our local http server using the example:
http://localhost:3000/?colour=red
The request will fail:
Failed to fetch
-
Successful request
Code: uncomment the
Access-Control-Allow-Origin
header on the http server.The wildcard value (
*
) instructs the browser to allow access to the response from any origin i.e. it allows the JavaScript running on any Browser in the browser to access the response.Access-Control-Allow-Origin: *
Send the example request again.
This time the response is displayed and the response returns with a status of 200 (success).
{ "name": "red", "hex": "#ff0000" }
-
CORS: HTTP header-based protocol
Cross-Origin Resource Sharing (CORS) allows requests that SOP would otherwise prevent.
Code: comment out the
Access-Control-Allow-Origin
header on the http server again.Send the example request. Once again, the request fails.
Open up browser development tools. In the console you will see a message like this:
Access to fetch at 'http://localhost:3000/?colour=red' from origin 'http://127.0.0.1:1234' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
The
CORS error
is also recorded in Status field of the network tab.Now check the server console.
You will see that although the browser has blocked access to the response, the server processed the request, and send a valid response.
A matching requesting origin
Code: change the value of
Access-Control-Allow-Origin
so that the origin matches that of the requesting origin (the web browser in the browser).You can compare the values of origin by clicking on the request in the network tab (Tip: view the headers in raw mode).
Request Header
Origin: http://127.0.0.1:1234
Response Header
Access-Control-Allow-Origin: http://127.0.0.1:1234
The allowed origin now matches the
Origin
3 passed in the request (set unless specified otherwise by the browser).The request response is once more accessible.
A non-matching requesting origin
Code: change the value of
Access-Control-Allow-Origin
so that either the scheme, domain or port does not match e.g. usehttps
.Note that the request is always returned by the http server and is visible in the http server console.
Access-Control-Allow-Origin: https://127.0.0.1:1234
NB The value of the port (1234) can vary. Check the value in the client browser address bar. The match must be exact;
localhost
is not the same as127.0.0.1
and vice versa.It is not possible to enter multiple origins but it is possible to check a URL against an allowed list. If there is a match, the request origin is set as the allowed origin.
CORS summary
Cross-Origin Resource Sharing (CORS) is a security mechanism implemented by browsers. The value of
Access-Control-Allow-Origin
affects whether or not the browser passes the response onto the web browser client.It is not a way to safeguard access to the API - the server returns a response no matter the
Access-Control-Allow-Origin
value. -
Clients other than browsers
Terminal: open a new terminal window.
Clients other than browsers can access a request resource regardless of the value of
Access-Control-Allow-Origin
. This can be verified by replicating a request for which the response is unavailable to a web browser from the command line. One way to do this is by using cURL:curl -i http://localhost:3000/?colour=red
The response is logged out to the terminal.
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Connection: keep-alive Keep-Alive: timeout=5 Content-Length: 30
-
Setting the value of mode in the request header
Code: set the value of mode to
no-cors
in the client.By default, the value of
mode
in the client header iscors
. This value appears as Sec-Fetch-Mode on the network tab in Chrome DevTools.Changing this value to
no-cors
has no effect on the server response headerAccess-Control-Allow-Origin
.no-cors
instructs the browser not to send the origin of the request. As a result, the response cannot be accessed. See the section on no-cors in the appendix for reasons why you might want to do this.The server response is also different:
Response status: 0
-
POST request
Browser: click on the FETCH POST tab.
A standard post has no CORS requirements other than that
Access-Control-Allow-Origin
permits the request.NB The
POST
request will be made even when the response from the server is unavailable! -
Preflight request
Code: uncomment the content type header in the client.
Where a request can cause side-effects, the browser will send a preflight request to the server to check whether the request will be fulfilled. This request is generated by the browser, not the developer, and has a method of type
OPTIONS
.As stated, a standard
POST
does not generate a preflight request. If the client request sets certain headers, however, a preflight request may be made.headers: { 'Content-Type': 'application/json', },
This client request header will result in a CORS warning. As a consequence, the
POST
request will not be sent.To allow this header, it is necessary to explicitly accept the header type on the server:
Code: uncomment the
Access-Control-Allow-Headers
setting on the server.res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
The preflight
OPTIONS
request now passes and thePOST
request is also sent.Status code
When a new colour is added, the status is 201 Created.
If the colour exists, the status is 409 Conflict.
-
Form submission
Browser: click on the FORM POST tab.
Form submissions are unaffected by CORS. No preflight request will be sent.
To prevent Cross-Site Request Forgery (CSRF), the server must be defended against malicious actions. This topic is not covered here.
CORS only triggers a preflight requests for requests not regarded as "simple". This term is no longer used but is useful nonetheless.
See the section on Simple requests in the appendix for more detail.
Summary
In this workshop we set up a simple http server, an instance of Node.js's http.Server.
From a web browser, we made a number of GET
and POST
requests against this server using the fetch API. We also posted data from an HTML form without JavaScript.
We experimented with headers on both the server and client in order to demonstrate that CORS is enabled by configuring the server but is used by the browser to control whether or not a web browser (and the JavaScript running on it) has access to the response.
Browser policy mechanism
There is a lot of confusion around CORS and LLMs are no more reliable than many other sources. This summary from Claude, however, is correct:
CORS is better described as a "browser policy mechanism" rather than a "security feature". It's designed to enforce the Same-Origin Policy (SOP) in browsers, but doesn't provide true security against malicious actors.
What CORS is about
- Browser policy enforcement
- Controlling which web applications can interact with your API directly from browsers
- Developer experience (allowing legitimate cross-origin requests when needed)
What CORS isn't about
Claude
- Preventing server-to-server attacks
- Protecting against stolen credentials
- Real security against determined attackers
Related workshops
How to…
Further reading
Appendix
-
no-cors
There are valid reasons for setting mode to
no-cors
in a request header. This is a verbatim answer from Claude made in response to my question asking why I might want to set mode to no-cors:The 'no-cors' mode in a client request header is used in a few specific scenarios:
- Cross-Origin Resource Sharing (CORS) Restrictions: If you're making a request to a resource that is hosted on a different origin (different domain, subdomain, port, or protocol) than your web application, the browser may block the request due to CORS restrictions. Setting the mode to 'no-cors' allows you to bypass these restrictions and make the request, but it also means you won't be able to access the response data in your JavaScript code due to the same-origin policy.
- Accessing Resources from Content Delivery Networks (CDNs): When making requests to resources (e.g., fonts, images, scripts) hosted on a CDN, you may need to use 'no-cors' mode to bypass CORS checks and load the resources correctly in your web application.
- Interoperability with Legacy Systems: Sometimes, you may need to interact with older or legacy systems that don't properly implement CORS headers. Setting 'no-cors' mode can help you work around these issues and make the necessary requests.
-
Simple requests
This is a helpful summary from ChatGTP:
Preflight requests are only sent for cross-origin requests that don’t meet the criteria for "simple" requests, such as when:
- The request method is not GET, POST, or HEAD.
- The request includes custom headers beyond a few permitted ones (Content-Type, Accept, etc.).
- The Content-Type is not one of the following allowed types:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
-
The difference between origin and referer
The
Origin
is the origin of the request.The
Referer
contains the absolute or partial address from which a resource has been requested. It can therefore contain the path, querystring in addition to the origin.