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
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:
- 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:
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
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://<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
Without this, a
fetch() call in LWC fails, and the error object has the message “
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
Access-Control-Allow-Origin HTTP headers.
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
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.
A value of
* denotes that any origin can access the response.
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.
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.