Stolen Ideas
Thoughts and learnings about software development
CORS... What? Why? How?
•
CORS
What is CORS?
Cross Origin Resource Sharing (CORS) is a web standard created to allow servers to define which origins other than its own are allowed to access its resources. This allows web clients to make http requests to services hosted on different origins.
Why is CORS?
By default, web browsers define a same-origin policy that prevents client code requests to different origins. This is a critical security control that helps isolate potentially malicious web documents from accessing third party services. CORS was created to help work around this policy by enabling the server to define which origins can access it.
How is CORS?
Let’s enable CORS for a cross-origin request between two locally running services. One service (our client) is running on http://localhost:1111, the other service (our api) is running on http://localhost:2222. Our api contains a single get endpoint /hello which when called responds with a 200 response code and a response body of hello there!. Our client page contains a title and a single click me button that when pressed, will make a get request to our api’s /hello endpoint and will print the response text to the console.
Given that this is a cross-origin request, when we press the click me button, instead of printing hello there! to the console, instead we see the following error message:
The browser has detected a cross-origin request and in doing so checks for an Access-Control-Allow-Origin response header in the server response. If one is not present then the server response is rejected. Given that this is a response header, we need to update our server to include this. This can be done by defining our own custom middleware, or by using the popular cors library. I’ll include examples of using both:
Now when we restart our server once more and press the click me button on the client we will see the expected api response in our console log:
How do I allow requests from multiple origins?
The Access-Control-Allow-Origin header only accepts a single origin. If your api needs to be used by multiple clients on different origins you may need to add additional behavior in your cors middleware to enable this. We can update our cors middleware to accept requests from both http://localhost:1111 and http://localhost:1234.
The two implementations above are similar, but how they handle an invalid origin slightly differs. In the custom middleware, we still accept and continue processing the request, whereas the cors library will throw an Error and we’ll stop processing the request. The client call will still fail in both cases, however the response status code would be 200 in the former case, and 500 in the latter case. Throwing an error would be the safer action in this case as this helps protect against CSRF attacks. CSRF is beyond the scope of this article.
Side effects of allowing multiple origins
A side-effect of allowing multiple origins is that the Access-Control-Allowed-Origin header may vary between requests which can cause caching issues.
In order to protect against these issues, you should add the Vary response header with the value Origin. This instructs any proxy server to consider the Origin header in a request when deciding to use a cached response.
In some cases you may be building a public api and want any website to be able to reach your service. This is much simpler than allowing only a sub-set of origins and doesn’t incur any caching side-effects. In our custom middleware, we just give Access-Control-Allow-Origin the value *. In the cors library, we just don’t provide an origin in our cors options (we could also not define any options in this case as it’s an optional parameter).
At some point in time your client requests might become complex enough that before sending the request, the browser will first send a pre-flight request to the server to see if the client request has an allowed method and headers prior to sending the actual request. For more information about what triggers the browser to send the preflight request, see here.
Our client has been updated to also send a correlation id header in our api requests. Now when we try pressing the click me button we see the following:
We need to add the blocked header to our Access-Control-Allow-Headers in our pre-flight response. From now on we’ll just show the relevant cors code that needs to be udpated.
If you’re not sure what custom headers your clients might include and you don’t care what they are, the pre-flight request includes the request headers that will be used so you can update Access-Control-Allow-Headers to the following:
The following headers may be used in preflight checks:
header
description
Access-Control-Allow-Origin
This header is also used in preflight requests
Access-Control-Allow-Credentials
Indicates whether the response can be shared the the request includes credentials
Access-Control-Allow-Methods
Indicates which methods are supported with cross-origin requests
Access-Control-Allow-Headers
Indicates which headers are supported with cross-origin requests
Access-Control-Max-Age
Indicates the number of seconds Access-Control-Allow-Methods and Access-Control-Allow-Headers can be cached
Conclusion
That about covers the basics of CORS. Hopefully this shed some light on what CORS is, why it exists, and how to start making cross-origin requests to your apis.
If you’re interested in learning more about CORS I would recommend this book by Monsur Hossain which goes into great detail on how to effectively enable your services for cross-origin requests.