The fundamental capabilities of the internet can be boiled down to one simple interaction: a call and a response. One machine (the client) sends requests to another machine (the server), which responds with a reply. This back-and-forth request-and-response cycle is how every phone, television, smart fridge, or computer sends and receives data.
However, there are several security risks inherent in this model, from application vulnerabilities to DDoS attacks. In this post, we’ll take a closer look at how Cross-Origin Resource Sharing, or CORS, mitigates these risks and how it operates. By working with a small Node.js app that interacts with several Salesforce APIs, we will see the differences in responses when CORS is enabled and disabled, as well as what effect it has on our client.
First, let’s take a deeper look at what CORS is and why it exists.
Whenever a browser requests a web page, the server sends back a chunk of HTML. The browser then draws that HTML and, crucially, continues to ask the server for more information when necessary. For example, if there’s any content that can’t be parsed, such as images or videos, or code that needs to be executed (such as JavaScript), then the browser continues making requests to the server, and the server happily fulfills them.
Suppose you’re browsing around at salesforce.com, and your browser encounters some JavaScript that executes a request to trailhead.com. For example, say a piece of code wants to get a list of a user’s completed trails using the fetch API:
let response = await fetch('https://api.trailhead.com/user/1/trails');
On the surface, this JavaScript code is pretty innocuous, but it presents a fundamental question about how the web operates: If a browser is loading salesforce.com, should it also be able to load data from trailhead.com? One’s instinct might be to say, “Yes, of course.” But if we allow salesforce.com to load from trailhead.com, why not allow loading code from heroku.com, github.com, or google.com? Why not just load code from anywhere?
Walking the slippery slope of making requests from any site in the world also introduces the security risks mentioned above. In this example, without any boundaries, trailhead.com can’t control who is requesting data from them. What would be ideal is if a server could make a list of whom it trusts to be given access out on the web. Perhaps trailhead.com will allow users on salesforce.com to load scripts, but not from anywhere else.
CORS is designed to plug this hole. A server dictates not only which domains can access its resources, but the types of HTTP verbs as well. If a client isn’t able to satisfy the server’s CORS requirements, then data cannot be executed or delivered. CORS is a concept that extends beyond any one programming language and is configured on a web server, such as nginx or Apache. Most crucially, it’s enforced by the browser: there’s no way around it, and data enforced by CORS can’t be accessed by browser clients outside of that context. CORS is designed to walk the line between enabling a client’s productivity and enforcing a server’s security.
CORS is enforced through HTTP Headers, which can be thought of as additional metadata attached to HTTP requests and responses. You might be familiar with adding headers for authentication (such as Authorization: token xxx
) or to specify the data types the client understands (via Accept). CORS has its own category of headers, which a server uses to state where a browser may load its resources from.
To demonstrate what this means in practice, let’s take a look at how a demo app using the Salesforce platform interacts with CORS.
Before getting started, make sure that your Salesforce instance supports access to its API. If you don’t have access to the Salesforce platform, then you can create a free Developer Edition account. You can use this org to test what developing on the Salesforce platform looks like.
Open a browser tab and navigate to https://jsfiddle.net. In the box that says JavaScript, paste these lines of code:
const run = async () => {
const response = await fetch('https://myInstance-dev-ed.my.salesforce.com/services/data');
const responseJson = await response.json();
responseJson.forEach( (e) => document.write(e.label + "<br/>"));
document.body.style.background = "white";
}
run();
Replace myInstance-dev-ed
with the actual name of your Salesforce instance. Click Run in the top menu bar. You should see….nothing.
However, if you open the Browser Console, you’ll see a message indicating that CORS has blocked access:
Access to fetch at
'https://myInstance-dev-ed.my.salesforce.com/services/data' from origin
'https://fiddle.jshell.net' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What’s going on here? Well, Salesforce provides semi-open access to its data and assets. In this case, we tried to use JavaScript’s fetch function to make a request and retrieve some JSON data. However, due to CORS, we were blocked from doing so. Unauthorized access is exactly what CORS was designed to prevent.
Specifically, we were blocked by the lack of a proper Access-Control-Allow-Origin definition. This header states which URLs a browser is permitted to load resources from. If this information is missing, then the browser simply cannot load the external resource, which in this case was the content at the /services/data
path of our Salesforce instance. There are many other types of CORS headers, such as Access-Control-Allow-Methods, which defines which HTTP methods a browser is permitted to make.
Fortunately, it doesn’t take much to enable CORS on the Salesforce APIs. Follow the short steps in this guide, and enter https://fiddle.jshell.net
as the allowed domain:
Make sure there’s no trailing slash at the end of this URL.
Click Save, then run that code snippet one more time. Voila! It’s not much, but you should see a list of some Salesforce editions.
While Salesforce takes the utmost effort to provide a way to securely retrieve data, other platforms might not be as secure. Fortunately, CORS also provides the ability to pass in credentials.
Credentials are useful if you’re navigating on a site that, unlike Salesforce, does not use an authentication mechanism. By setting an additional option in your client-side JavaScript request (credentials: "include"
), your browser’s cookies are sent over along with the request. Cookies have been used as an identifying mechanism since the very early days of the internet. Although they’ve largely been abandoned as an authentication method, some websites still do use them as such. It’s the server’s responsibility to receive the cookies and perform any additional validations on them.
Since CORS support is largely a consideration for servers (that is, servers should have CORS enabled by default), there isn’t much else for the client to do. There are different modes that a client can set, which have a variety of purposes. For example, if you’re building a series of microservices, then you may find that the same-origin
’s strict domain matching may be useful for doubly ensuring that requests and responses come from the same domain.
We’ve largely been talking about CORS and JavaScript, but there’s another important component to web development where CORS can step in: the humble <iframe>. The iframe element allows you to embed the contents from another URL into a web page. What keeps me from registering www.not-salesforce.com and just setting the page to the contents of Salesforce? CORS, of course!
Ultimately, not every server supports open CORS access, and it’s at the server’s discretion. Improperly configured CORS settings have the potential to impede legitimate user workflows, but disabling CORS entirely will certainly make your application more vulnerable to attacks from malicious clients. In general, as a developer, if an action can be done on the server, it’s more likely to be safer than performing work on the client, so make sure you are disabling CORS for a good reason if you’re thinking of it.
We’ve just taken a brief look at Salesforce APIs here. You can also manipulate data or retrieve all sorts of Apex data. We encourage you to take a look at the Force.com REST API Developer Guide for more information on what the APIs allow you to do. You can also find a bevy of tutorials on Trailhead to help you build your integrations with the platform.