When firing an API request from Lightning Web Components (LWC), have you ever run into errors like “Refused to connect because it violates the document’s Content Security Policy” or “Access has been blocked by CORS policy”? In this blog post, we’ll explore the reason behind these errors and how to fix them.

The same-origin policy

Modern web browsers have built-in security mechanisms that provide an added layer of security to your web applications, at the heart of which is the same-origin policy (SOP).

The same-origin policy prevents scripts on pages served by one origin (e.g., codey.com) from accessing resources, such as DOM elements of an Iframe or APIs from another origin (e.g., astro.com). The origin of a request is the domain name of the page sending the request, which is determined when the page is first loaded into the browser. All subsequent requests are checked against this domain name, and only if the request is to the same domain can you access the response. This helps reduce a few types of Cross Site Request Forgery (CSRF) and Cross Site Scripting (XSS) attacks. The origin of requests fired from LWC components embedded in Lightning Experience is typically <mydomain>.lightning.force.com.

Below is an example of requests governed by the same-origin policy. This sequence diagram shows a browser making a GET request to codey.com for first page load. Codey.com responds with index.html, so the origin for the page is therefore codey.com. The browser then makes a GET call to codey.com/api/getData, which successfully returns a response. The browser then makes a cross-origin API call to astro.com/api/getData that is not allowed by SOP.

There are two important things to remember about the same-origin policy:

  • First, it is only applicable for requests made via JavaScript code in a web browser. Requests made outside the web browser, such as curl, server-to-server communication, or non-script requests from the browser (like HTML form submits, embedding external images, and scripts in an HTML page) aren’t bound by the same-origin policy.
  • Second, this policy doesn’t prevent a request from being made. It just prevents the script from accessing the response.

So, how do we prevent a browser from making a request to another origin in the first place, and how do we allow the scripts to read the response to a request to another origin? That’s where the Content Security Policy (CSP) and Cross-Origin Resource Sharing (CORS) come into the picture. CSP defines what data can be loaded on a page, and CORS defines what data other pages can load from it.

SOP, CSP, and CORS together give an additional layer of security to API requests by defining what requests can be sent by a browser, and what responses can be read by the browser. Let’s dive a little deeper.

Content Security Policy

Content Security Policy (CSP) prevents a website itself from loading content from a third party (i.e., a different origin). This is defined either via the Content-Security-Policy HTTP header or by using the HTML meta tag <meta http-equiv="Content-Security-Policy">. You can use different policy directives to control which domains different resources can be loaded from. For example, to specify that images can only be loaded from an Amazon S3 bucket, and API calls can only be made to myapi.astro.com, you can define the directive below:

Content-Security-Policy: connect-src 'self' 'myapi.astro.com';
                         img-src 'self' 's3.amazonaws.com';

Don’t forget to add self to the directives, because it may prevent the site from loading resources from itself (i.e., the same origin). These headers must be set by the server during the first page load. Similar to SOP, CSP is relevant only for requests made from a browser.

Here is how the previous example would look when a CSP header that only allows calls to self and astro.com is added to the web page. CSP would prevent requests to an endpoint on ruth.com. This example doesn’t show the responses to the requests fired.

Enforcing CSP in LWC

Every page in Lightning Experience has default CSP headers. For example, one of the directives included is script-src 'self', which ensures that only scripts from the same origin can be called. This is the reason why you need to upload third-party scripts as Static resources to use them in LWC. Static resources in Lightning Experience are served from the lightning.force.com domain, which is the same as a Lightning Experience page.

Many other Salesforce domains and subdomains like https://static.lightning.force.com, *.visualforce.com, and https://<mydomain>--c.documentforce.com are also safe-listed under different directives.

Here is a screenshot with the complete list of CSP directives used on a Lightning page.

When making an API callout to a third-party endpoint from LWC, you can add its domain to CSP Trusted Sites from Salesforce Setup and ensure that the connect-src checkbox is checked. This ensures that this domain is automatically added to the connect-src directive.

Without this, a fetch() call in LWC fails, and the error object has the message “Failed to fetch".

 fetch('https://lwc-callout-demo.herokuapp.com/sampleget')
    .then((response) => {
        // Never executed if the site is not in the CSP Trusted list
    })
    .catch((error) => {
        console.error(error); // TypeError: Failed to fetch
    });

The error object doesn’t have any information about the CSP failure. It is only logged in the browser console.

Cross-Origin Resource Sharing

While CSP can be used to allow a website to make a request to a safe-listed third party, it is up to the third party to allow a given origin to read the response to the request. This is enforced using Cross-Origin Resource Sharing (CORS).

CORS is a way to relax SOP. CORS allows a server to define which origins a particular resource is allowed to be accessed from. It is enforced by the browser via the Origin and Access-Control-Allow-Origin HTTP headers.

The Origin header is sent by web browsers along with a request that indicates where the request has come from. Non-browser requests typically don’t include the Origin header.

Origin: 'codey.com'

The Access-Control-Allow-Origin header is sent by the server along with the response that indicates which origin should be able to access the response.

Access-Control-Allow-Origin: 'codey.com'

A value of * denotes that any origin can access the response.

Access-Control-Allow-Origin: *

Once a response is received by a browser, it checks for the Access-Control-Allow-Origin header on the response. If it is present, and the value is either the current origin or is *, the response is allowed to be accessed by the script. If not, the request fails with a CORS error. A CORS error prevents your script from accessing the response of the request.

Similar to SOP, CORS is also enforced by a web browser and is not applicable to the requests made outside the web browser.

CORS works slightly differently for different types of API requests. The requests from a browser can be of two types: simple requests and preflighted requests. The browser automatically determines the type of request based on the Request method, Content Types, and Headers.

Simple requests

In the case of simple requests, the actual HTTP request is immediately fired. Here is an example of a successful simple request. The server checks to see if the incoming request’s origin is safe-listed. If yes, it processes the request and returns a response along with the Access-Control-Allow-Origin header containing the origin’s domain. The browser then allows the script to read the response.