I want to build a Single Page Application (SPA) and noticed the following issue during separation of backend API (REST) and frontend assets (static vue.js code):
When serving the index.html from another domain than the API backend, most of the POST/PUT requests trigger a CORS preflight request.
I did some research and found out that blog posts [1][2] discuss this issue - without giving a practical solution. Some headers (e.g. Authorization header and the Content-Type header with the application/json value) are not allowed as cors-safelisted-request-header. Thus POST/PUT requests trigger a CORS preflight request. This is not acceptable since it adds a considerable amount of latency.
Is it possible to avoid these preflight requests if both domains are owned by the same entity?
I did some research on how to avoid CORS requests between frontend and backend. The solution requires the index.html file being served from the same domain as the REST API backend (see Example below). I wonder if not using separate domains is the only solution to avoid CORS requests for SPAs.
[1] https://www.freecodecamp.org/news/the-terrible-performance-cost-of-cors-api-on-the-single-page-application-spa-6fcf71e50147/
[2] https://developer.akamai.com/blog/2015/08/17/solving-options-performance-issue-spas
Use a proxy to avoid CORS errors To use the public demo of cors-anywhere, just add the url you want to make the request to after the domain e.g. https://cors-anywhere.herokuapp.com/https://cat-fact.herokuapp.com/facts (*if you view this in the browser you might get an error about a missing request header.
A CORS preflight OPTIONS request can be triggered just by adding a Content-Type header to a request — if the value's anything except application/x-www-form-urlencoded , text/plain , or multipart/form-data .
How to avoid CORS preflight requests in Single Page Applications?
First, you cannot "avoid" something which is part of the standard. Second, this question is wrongly stated, as SPAs themselves can use or not use CORS. This entirely depends on your set-up/design. If you want to avoid preflighting, just do not request resources from another origins.
Is it possible to avoid these preflight requests if both domains are owned by the same entity?
No.
Site ownership has nothing to do with CORS. Cross-Origin Resource Sharing means that you want to share resources between different origins. Not necessarily between different domains. Origin is not entity nor owner. Origin is just a part of a URL.
The best solution is to avoid introducing the CORS problem altogether and always stick to the same-origin. Your assumption that you should separate back-end API from front-end assets with different origins is not true.
Introducing multiple origins and domains to your setup is adding more problems than it solves. Ideally your whole setup should be hidden from connecting clients and reduced to a single domain and origin.
The setup you want
-> static file server
CDN -> Load balancer -> api server
-> api server
-> ...
Example configuration
Use your load balancer and its ACL rules. Tell it to route all traffic where it should go. I will use haproxy as an example, because that is what I use, and because from what I know this is pretty much industry standard in terms of software load balancing solution.
This is not the whole config, just a relevant part concerning routing traffic.
# part of haproxy configuration file, usually located at /etc/haproxy/haproxy.cfg
frontend http-in # this is where requests get in to load balancer
bind *:80
acl data path_beg /api # "catch" any request with path beginning with "/api"
use_backend api if data # then route it to api backend defined below
default_backend static # any non-matching request we direct to static file server
backend static
server node1 127.0.0.1:3000 # server hosting static files (index.html)
backend api
server node1 127.0.0.1:4000 # application servers
server node2 ...
There are not many options there.
The simplest solution would be to deliver the html and assets from the same domain as your API.
The second options is to use only headers that are cors-safelisted-request-header.
What I noticed:
The Content-Type
header can be replaced with the Accept
header. This header is okay.
If you are doing XHR requests, you can omit the Authentication
header and instead add the authentication infos automatically by setting the withCredentials
field of your XHR request to true. VanillaJS example:
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.example.org/api/whatever', true);
xhr.withCredentials = true;
xhr.send();
If you are using any other XHR client, consult the docs if the option can be set.
Another option would be to authenticate with cookies and server side sessions. As you are using AWS, AWS Cognito might be an option.
If there are more headers in use that are not a safelisted CORS header, you have to get rid of them.
Access-Control-Max-Age may help:
The
Access-Control-Max-Age
response header indicates how long the results of a preflight request (that is the information contained in theAccess-Control-Allow-Methods
andAccess-Control-Allow-Headers
headers) can be cached.
...Actually the link you've posted mentions it:
You might say yes. We can use the Access-Control-Max-Age header to cache the results of a preflight request.
Then they go on with caveat:
The way preflight cache works is per URL, not just the origin. This means that any change in the path (which includes query parameters) warrants another preflight request.
But you can keep using the same URL and send all parameters in the request body.
Also
Is it possible to avoid these preflight requests if both domains are owned by the same entity?
No. There is no practical way for a browser to check ownership of domains. Sometimes even for a human it's not an easy task.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With