html code

Utilizing CORS and CSP for Accessing APIs in LWC

Have you encountered issues such as “Refused to connect because it violates the document’s Content Security Policy” or “Access has been blocked by CORS policy” when making API requests from Lightning Web Components (LWC)? In this article, we will delve into the causes of these errors and provide solutions to resolve them.

Have you ever encountered issues such as “Encountered a Content Security Policy violation” or “Access blocked due to CORS policy” while making API requests from Lightning Web Components (LWC)? In this blog post, we will investigate the causes of these errors and provide solutions for resolving them.

The policy of same-origin restrictions

Contemporary web browsers incorporate inherent security mechanisms that augment the protection of your web applications, with a central component being the  same-origin policy (SOP).

The same-origin policy serves as a safeguard against scripts on pages from one origin (for instance, codey.com) attempting to access resources, such as DOM elements within an iframe or APIs from a different origin (e.g., astro.com). The origin of a request is defined by the domain name of the page that initiates the request, established when the page is initially loaded into the browser. Subsequent requests are validated against this domain name, allowing access to the response only if the request is made to the same domain. This protective measure is effective in mitigating several types of security risks. Cross Site Request Forgery (CSRF) and  Cross Site Scripting (XSS).attacks. Requests initiated from LWC components embedded in Lightning Experience typically originate from <mydomain>.lightning.force.com.

Here’s an illustration of requests regulated by the same-origin policy. In this sequence diagram, a browser initiates a GET request to codey.com for the initial page load. Codey.com responds with index.html, establishing codey.com as the origin for the page. Subsequently, the browser executes a GET call to codey.com/api/getData, and it receives a response as expected. However, when the browser attempts to make a cross-origin API call to astro.com/api/getData, it encounters a restriction imposed by the same-origin policy, preventing the operation.

Two key points to keep in mind regarding the same-origin policy are:

  1. The policy exclusively applies to requests initiated through JavaScript code within a web browser. Requests made through means external to the web browser, such as server-to-server communication, tools like curl, or non-script requests like HTML form submissions, embedding external images, and scripts within an HTML page, are not subject to the same-origin policy.
  2. The policy does not hinder the act of making a request. Rather, it restricts the script’s ability to access the response.

So, what measures can we implement to stop a browser from initiating a request to a different origin in the initial stage, and how can we enable scripts to access and process the response from such requests to another origin? This is where the Content Security Policy (CSP).and Cross-Origin Resource Sharing (CORS). come into play. Content Security Policy (CSP) delineates the types of data that can be loaded on a page, while Cross-Origin Resource Sharing (CORS) specifies what data can be retrieved by other pages from it.

The combination of the Same-Origin Policy (SOP), CSP, and CORS collectively enhances the security of API requests by determining which requests a browser can transmit and which responses it can access. Let’s explore this concept further.

 

Policy for Ensuring Content Security

The Content Security Policy (CSP) blocks a website from loading content from an external source, typically a different origin. This policy is specified through either the Content-Security-Policy HTTP header or by employing the HTML meta tag <meta http-equiv=”Content-Security-Policy”>. You have the flexibility to utilize different policy directives to manage the domains from which various resources can be fetched. For instance, if you want to restrict image loading to only come from an Amazon S3 bucket and limit API calls to myapi.astro.com, you can establish the following directive:

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

Be sure to include “self” in the directives because omitting it may hinder the site from loading resources from its own origin. These headers need to be configured by the server when the page is initially loaded. Much like the Same-Origin Policy (SOP), Content Security Policy (CSP) exclusively pertains to requests generated from within a web browser.

In the context of the previous example, when a CSP header is introduced on the web page, permitting calls only to “self” and “astro.com,” CSP would prohibit requests to an endpoint on ruth.com. It’s important to note that this example does not illustrate the responses to the requests made.

Implementing CSP in Lightning Web Components (LWC)

In Lightning Experience, each page comes with predefined Content Security Policy (CSP) headers. For instance, one of these directives is “script-src ‘self’,” which ensures that only scripts originating from the same source can be invoked. This is why third-party scripts need to be uploaded as Static resources for use in Lightning Web Components (LWC). In Lightning Experience, Static resources are delivered from the lightning.force.com domain, which matches the domain of a Lightning Experience page.

Several other Salesforce domains and subdomains, such as https://static.lightning.force.com, *.visualforce.com, and https://<mydomain>–c.documentforce.com, are also whitelisted under different CSP directives.

Below is a screenshot displaying the complete set of CSP directives employed on a Lightning page.

When conducting an API callout from LWC to an external endpoint, you have the option to include its domain in the configuration.CSP Trusted Sites Within the Salesforce Setup, you should verify and ensure that the “connect-src” checkbox is selected for the specific domain. This action automatically incorporates the domain into the “connect-src” directive.Failure to do so will result in the fetch() call within LWC being unsuccessful, and the error message “Failed to fetch” will be generated.

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 does not contain any details regarding the Content Security Policy (CSP) failure. Such information is solely recorded in the browser’s console.

Cross-Origin Resource Sharing (CORS)

While CSP can be employed to permit a website to initiate a request to an origin on a predefined safe list, it is ultimately the responsibility of the third party to authorize a specific origin to access and retrieve the response from the request. This authorization mechanism is implemented through Cross-Origin Resource Sharing (CORS).

CORS serves as a means to ease the constraints imposed by the Same-Origin Policy (SOP). It enables a server to specify which origins are permitted to access a particular resource. The enforcement of CORS is carried out by the browser using the Origin and Access-Control-Allow-Origin HTTP headers.

The Origin header is transmitted by web browsers in conjunction with a request to indicate the request’s source. Typically, non-browser requests do not include the Origin header.

Origin: 'codey.com'

The Access-Control-Allow-Origin header is included in the response sent by the server, specifying the authorized origin that can access the response.

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

An asterisk (*) signifies that any origin is permitted to access the response.

Access-Control-Allow-Origin: *

After a browser receives a response, it examines the Access-Control-Allow-Origin header in the response. If the header is present and its value matches either the current origin or is set to an asterisk (*), the response is authorized for access by the script. If not, the request will result in a CORS error. A CORS error prohibits your script from accessing the response of the request.

Much like the Same-Origin Policy (SOP), CORS is enforced by web browsers and pertains exclusively to requests made within the web browser environment.CORS operates with some variations depending on the type of API requests. Requests originating from a web browser can be categorized into two types: simple requests and preflighted requests.The browser automatically discerns the request type by analyzing the Request method, Content Types, and Headers.

Basic Requests

In the scenario of basic requests, the HTTP request is executed promptly. An instance of a successful basic request is as follows: The server examines whether the origin of the incoming request is on the whitelist. If it is, the server handles the request, responds with an Access-Control-Allow-Origin header containing the domain of the origin, and the browser subsequently permits the script to access and read the response.

Below is an instance of a basic request that encounters a CORS error. It’s crucial to understand that the presence of a CORS error doesn’t imply that the request hasn’t been transmitted. In reality, the request is dispatched, and due to the absence of an Access-Control-Allow-Origin header in the response, a CORS error is encountered, leading to the denial of access to the response by your script.Interestingly, whether or not a specific action is carried out on the server as a result of your request depends entirely on the implementation of the API. For instance, some APIs might not execute their underlying functionality due to a failed origin check, whereas others may execute actions while omitting the Access-Control header in the response. This behavior is often observed in cases involving APIs. fire-and-forget requests.

Pre-flight Requests

For preflighted requests, an initial “CORS Preflight” request is dispatched to the server to ascertain whether the actual request will be permitted, considering factors such as the Origin, Methods, and so on. Only when the preflight request succeeds will the actual request be initiated.

Here is an instance of a successful preflighted request.

Below is an illustration of an unsuccessful preflight request. The preflight request is unsuccessful because the value in the Access-Control-Allow-Origin header, received from astro.com, doesn’t correspond to the origin of the request. Furthermore, there is a disparity between the values in the Access-Control-Request-Method header of the request and the Access-Control-Allow-Methods header from astro.com. As a consequence of the preflight request’s failure, the actual request is never transmitted to the server. Consequently, no server-side actions are triggered as a result of your request.

Handling CORS in LWC

Content Security Policy (CSP) is under our jurisdiction, allowing us to specify which resources Salesforce can access. However, Cross-Origin Resource Sharing (CORS) is regulated by the service provider, which is the owner of the third-party endpoint.

Are you the owner of the external API? If you are, you will need to make adjustments to guarantee the presence of the Access-Control-Allow-Origin header in your API’s responses. For instance, if you are utilizing the API, express framework,ou have the option to utilize cors npm packagefor enabling CORS.

import cors from "cors";

const safelist = ['http://example1.com', 'http://example2.com']
const corsOptions = {
  origin: safelist
}

app.use(cors(corsOptions));

However, it’s essential to bear in mind that non-browser requests do not include the Origin header. Therefore, it is important to handle these scenarios to ensure that your API functions correctly when not accessed via a browser.

If you do not have ownership of the external API… Depending on your specific use case and the API’s implementation, you may choose to either overlook the CORS error, suppress it, or entirely circumvent it.

Disregarding CORS Errors

You can opt to disregard a CORS error if you are certain that the API will receive and process your request, and you have no interest in the response (e.g., for fire-and-forget requests). It is crucial to note, though, that the then() block of your fetch call is not executed when a CORS error occurs. This situation gives rise to two issues.

Firstly, your script will remain unaware of whether your request was successful. Ordinarily, the response object contains a status property, which includes the HTTP status codesof the response’s status. Given that the then() block remains unexecuted, you will not have access to the response object, including its status property. Consequently, if your request results in a 401 error, your script will not have the means to detect it. Nevertheless, this information can be viewed in the Network tab of your web browser.

Secondly, you won’t have the capability to execute business logic that relies on the response’s status. If you have business logic that should run regardless of the callout’s success, you will need to place it within the finally block.

fetch('https://lwc-callout-demo.herokuapp.com/sampleget') .then((response) => { // never executed when a CORS error occurs }) .catch((error) => { console.error(error); // TypeError: Failed to fetch }) .finally(()=>{ // This gets executed successfully // irrespective of whether a CORS error occurs or not });

These issues are precisely why it’s generally advised against overlooking CORS errors.

Mitigating CORS Errors with the “no-cors” Approach

When you employ the “no-cors” option When you employ the “no-cors” option in a fetch request, the request will not result in a CORS error. In other words, the CORS error will no longer be displayed in the browser console, and the then() block is executed. However, akin to ignoring CORS errors, there are significant limitations when using the “no-cors” mode.

Firstly, even though the then() block is executed and you gain access to the response object, the response you receive is categorized as “opaque.” This implies that you will be unable to inspect the response body or headers. Additionally, the status is consistently reported as 0, rather than conforming to the typical HTTP status codes, and the ok property of the response is universally set to false, irrespective of the actual status.

You will need to address this response type in your code appropriately, which may involve utilizing the response.ok flag, for instance.

fetch('https://lwc-callout-demo.herokuapp.com/sampleget', {
        mode:'no-cors'
 })
    .then((response) => {
       // This executes 
        if (!response.ok) {
           // This executes 
           throw Error(response);
        } else {
            // Handle Response
        }
    })
    .catch((error) => {
        // This block executes because you are throwing an error 
        // in the if (!response.ok) block.
        // If not, this block isnt executed.
    });

Secondly, and of utmost significance, the “no-cors” mode also eliminates any unsafe headers,However, the “no-cors” mode comes with its limitations. It strips certain headers, including the Authorization header, from the request. It exclusively permits the use of the HEAD, GET, or POST methods and automatically transforms the content type of your POST request body into either application/x-www-form-urlencoded, multipart/form-data, or text/plain. Consequently, if you are accessing an endpoint that requires authentication via the Authorization header, employing the “no-cors” mode will lead to an authentication error. Additionally, the automatic conversion of the request’s content type may result in a malformed request.

Nonetheless, “no-cors” has its advantages. When making cross-origin GET or HEAD requests in “no-cors” mode, the Origin header is not included, thus avoiding the need for a preflight request and directly connecting with the API endpoint. This can ensure that APIs handle the request as if it were coming from a non-browser source, instead of rejecting it due to origin mismatch, depending on their implementation.

If the “no-cors” mode does not align with your use case due to the aforementioned limitations, the only viable option left is to employ a proxy between your Lightning Web Component (LWC) and the third-party API.

Overcome CORS errors by utilizing a proxy: Either through Apex or Heroku

As previously noted, CORS is a restriction imposed solely by the browser. Therefore, any solution that doesn’t rely on a browser effectively bypasses CORS. In most cases, it’s more secure to perform callouts from the server-side because a page’s JavaScript source code is readily accessible via a browser, potentially exposing any sensitive information or tokens used in your code when interacting with an endpoint.

Within Salesforce, the most straightforward approach for server-side callouts is to use Apex.Subsequently, you can invoke this Apex class from your Lightning Web Component (LWC). Check out this example to observe it in operation. However, if you’re not well-versed in Apex development, you can alternatively establish a Node.js proxy on Heroku that handles the API call on your behalf and appends the necessary CORS headers to the response before transmitting it to your LWC. It is imperative to ensure that the proxy is fortified with adequate security measures to prevent potential vulnerabilities and other forms of attacks.

What is the relevance of this to Salesforce APIs?

Making calls to Salesforce APIs from LWC hosted on Salesforce is not feasible. You will need to either utilize lightning/ui*Api Wire Adaptersor resort to employing Apex classes to execute those callouts. However, when conducting calls to Salesforce APIs using JavaScript or LWC hosted on external platforms, you can incorporate the domain of the external system into your configuration. Salesforce CORS allowlist.

When a request contains a whitelisted origin, Salesforce includes the origin in the Access-Control-Allow-Origin HTTP header, along with any supplementary CORS HTTP headers. In cases where the origin is not part of the safelist, Salesforce responds with an HTTP status code 403. It’s important to note that Salesforce employs this status code for various error scenarios, including insufficient permissions. Consequently, you should inspect the response’s status message to confirm whether it is indeed a CORS-related error.

Recap

Addressing CSP errors when making API calls from LWC is relatively straightforward, as you have control over the content that can be loaded on your web page. In contrast, resolving CORS errors requires more in-depth analysis to determine whether to disregard, mitigate, or circumvent them. Although overlooking and mitigating errors through the “no-cors” mode can be suitable for specific scenarios, you may find it necessary to establish Apex or Heroku proxies to effectively bypass the majority of CORS errors.

Below are some additional resources to aid you in delving deeper into these web security concepts: