Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid CORS preflight requests in Single Page Applications?

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.

Question

Is it possible to avoid these preflight requests if both domains are owned by the same entity?

Research

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.

Scenario (Example)

  • Single Page Application (SPA); frontend and backend layer
  • hosted in the AWS cloud
  • Layer 1: CloudFront CDN with an S3 bucket origin - serve static assets (Vue.js frontend) on static.example.com
  • Layer 2: Load-Balancer with ECS integration which is running node.js containers to host the (REST) backend on example.com
  • The communication between layer 1 and layer 2 uses the HTTPS protocol and the REST paradigm.
  • The index.html is served by layer 2 and customers open the webapp using example.com.
  • The index.html references the API by pointing to the same domain (example.com). It references the static vue assets by pointing to the CDN (static.example.com).
  • The SPA consists of two parts: a) the public assets (.js files, .css files etc.) and b) the index.html file. The latter one is served by the same fleet of backend containers which also host the REST API.

References

[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

like image 818
Martin Löper Avatar asked Apr 25 '20 14:04

Martin Löper


People also ask

How do you avoid CORS policy?

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.

What triggers preflight request?

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 .


3 Answers

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 ...

like image 144
Anastazy Avatar answered Oct 24 '22 21:10

Anastazy


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.

like image 29
mbuechmann Avatar answered Oct 24 '22 21:10

mbuechmann


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 the Access-Control-Allow-Methods and Access-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.

like image 38
x00 Avatar answered Oct 24 '22 20:10

x00