49

I want to call a Salesforce REST URL from a Lightning Component. Specifically, I'm trying to call this Analytics API URL: /services/data/v32.0/analytics/reports.

I see the following error in the JavaScript console:

Refused to connect to 'https://na15.salesforce.com/services/data/v32.0/analytics/reports' because it violates the following Content Security Policy directive: "connect-src 'self'".

I have a controller.js which calls to a helper.js which has the following in it in it. The sessionId is retrieved from an Apex controller that just returns UserInfo.getSessionId().

var action = component.get("c.getSessionId");
action.setCallback(this, function(a) {
    var sessionId = a.getReturnValue();
    console.log(sessionId);

    $j.ajax("/services/data/v32.0/analytics/reports", {
        beforeSend: function(xhr) {
            xhr.setRequestHeader("Authorization", "Bearer " + sessionId);
        },
        success: function(response) {
            alert('success!');
        },
        error: function(jqXHR, textStatus, errorThrown) {     
            alert(jqXHR.status + "," + textStatus + ": " + errorThrown);
        }
   }); 
});

$A.enqueueAction(action);

I'm positive that the REST request is valid as I'm able to make it successfully from a REST client (Postman).

I did see the Content Security Policy section in the Lightning Developer's Guide, but it doesn't seem to exclude Salesforce URLs:

The framework's CSP covers these resources:

JavaScript Libraries
All JavaScript libraries must be uploaded to Salesforce static resources. For more information, see Accessing JavaScript Libraries in Markup.

HTTPS Connections for Resources
All external fonts, images, frames, and CSS must use an HTTPS URL.

The security violation error message references a refusal to connect to na15.salesforce.com, even though I am just referring to a relative URL in the ajax call. Here is the csp report:

csp-report: {document-uri:https://na15.lightning.force.com/paura/widgets.app,…}
blocked-uri: "https://na15.salesforce.com"
document-uri: "https://na15.lightning.force.com/paura/widgets.app"
original-policy: "default-src 'self'; script-src 'self' https://ssl.gstatic.com/accessibility/ chrome-extension: 'unsafe-eval' 'unsafe-inline'; object-src 'self'; style-src 'self' chrome-extension: 'unsafe-inline'; img-src http: https: data:; media-src 'self'; frame-src https:; font-src https: data:; connect-src 'self'; report-uri /_/csp"
referrer: "https://na15.salesforce.com/_ui/common/apex/debug/ApexCSIPage"
status-code: 0
violated-directive: "connect-src 'self'"

Is it possible to access a Salesforce REST URL from a Lightning Component? If so, how and what am I missing?

6
  • 1
    For reference, here's a sample Content Security Policy header from my Lightning app: Content-Security-Policy:default-src 'self'; script-src 'self' https://ssl.gstatic.com/accessibility/ chrome-extension: 'unsafe-eval' 'unsafe-inline'; object-src 'self'; style-src 'self' chrome-extension: 'unsafe-inline'; img-src http: https: data:; media-src 'self'; frame-src https:; font-src https: data:; connect-src 'self'; report-uri /_/csp
    – Marty C.
    Commented Nov 6, 2014 at 1:01
  • Thanks Marty. Assuming, I'm doing everything else in my call alright, I'm sure that's it. The connect-src is 'self'. The security violation message I get refers to na15.salesforce.com, even though I'm not at that URL. I'll update the question with more details. I'm guessing the Salesforce URLs need to be whitelisted. Commented Nov 6, 2014 at 1:58
  • I believe a significant part of the problem is that when you're connecting to the relative URL, you're being redirected to the salesforce.com domain. For example, if you try to manually type in and open na15.lightning.force.com/services/data/v32.0/analytics/reports in your browser (having already logged in and opened up your app), you will notice that you're automatically redirected to na15.salesforce.com/services/data/v32.0/analytics/reports which matches the error message.
    – Marty C.
    Commented Nov 6, 2014 at 3:04
  • 2
    Hi Peter, I raised a followup question on this here salesforce.stackexchange.com/questions/68124/…
    – Uwe Heim
    Commented Mar 4, 2015 at 14:14
  • I saw that. The Lightning Component Developer's Guide has a section on API access now: developer.salesforce.com/docs/atlas.en-us.lightning.meta/… - Basically, what Marty answered. Commented Mar 4, 2015 at 15:44

5 Answers 5

53
+50

Marty is correct, although I would like to temper the description of this being "unfortunate" a bit with some background on why the security policy is in place. In order for us to satisfy our internal security audits and architectural reviews which allowed us, for the first time ever in our presentation layer, to provide a supported mechanism to allow salesforce authored code and customer/ISV/partner authored code to coexist in the same JavaScript space we had to tighten things down significantly. Here are some of the things currently enforced:

  • Content security policy, or CSP, a relatively new browser technology that allows us to have fine-grained control over access to all external references to scripts, images, stylesheets, etc. Our policy is extremely restrictive right now and not extensible but we had plans to open up a white listing capability in the near future.
  • Lightning applications are served from a separate domain in much the same manner that visual force pages are today. The difference here is that you are actually in the same application, running right next to our client-side code and not relegated to live in an iframe silo. Those silos have presented significant challenges around producing high performance, high fidelity, engaging user experiences that integrates directly into core salesforce functionality for many years.
  • The lightning application separate domain also uses a special lightning session ID that does not have direct API access. The fact that you can currently provide indirect access to a fully API capable session ID is a great example of why our content security policy is currently so restrictive. Leaking an API said back to the client in a way that malicious JavaScript can easily steal is precisely why we have things locked down so tightly today. What should happen if you attempted to serialize an API session ID from an apex controller? That session ID is supposed to be the same restricted access Aura session ID but we apparently have a leak in that underlying logic. Our belt and suspenders approach with CSP combined with serving from a separate domain actually thwarted this potential attack.

Why did we do these things? Most of it boils down to "protect Setup". Without these safeguards in place is almost trivial for malicious client side code to gain height and privileges and access to administrative level functionality by remote controlling the setup user interface Also, much of what we have done so far represent cross site scripting (XSS) attack countermeasures.

There is definitely much more to the story but hopefully this gives you some of the back story to better understand our decisions and direction. This is not the end, rather just the beginning, and we are hard at work on improving and refining our approach to balancing the security requirements that we have to satisfy along with providing all of you with the tools that you need to build what you want.

2
  • 6
    Thanks for the detailed and insightful explanation, Doug. Can you confirm two things: Right now, for developers (myself included) who need to be able to access the Salesforce REST API in our apps, is going through Apex the only way? And when you say "leak in that underlying logic", are you referring to the fact that going through Apex is currently possible but something that will be blocked in a future release or critical update?
    – Marty C.
    Commented Nov 6, 2014 at 16:43
  • Hi @MartyC. and Doug - I raised a followup question here salesforce.stackexchange.com/questions/68124/… - would be very important to learn if the API will be lightnified sooner or later. Without API several great patterns we love and need in Visualforce will not work in Lightning.
    – Uwe Heim
    Commented Mar 4, 2015 at 14:21
11

Peter, the unfortunate answer (I think) is that based on how Lightning currently works in Winter '15, we may not have a direct way of connecting to any of Salesforce's REST APIs. As a workaround, it appears that you can leverage Apex as a conduit to the REST APIs.

The approach can be summarized as follows:

  1. Create an Apex client for the REST API
  2. Create an Apex controller for your Lightning app
  3. Create your Lightning app, and bind it to the controller
  4. Specify an init handler for your app
  5. Configure the handler to enqueue the Apex controller's action to execute the REST API operation

I've confirmed that this approach works, at least in the sample app shown in this gist: https://gist.github.com/martyychang/6ef675e598838fa0013d

enter image description here

3
  • 2
    I believe this is the only approach supported because of the CSP rules. I'm investigating but it seems you need to use the Apex controller. Commented Nov 6, 2014 at 13:22
  • 4
    This definitely no longer works, the sessions Id generated by UserInfo.getSessionId() when the apex is running in the lightning context, returns this response:[{"message":"This session is not valid for use with the REST API","errorCode":"INVALID_SESSION_ID"}]
    – Evan
    Commented Feb 18, 2016 at 21:57
  • @Marty C., will we need a Remote End Point setup in user org pointing to the current domain name of the org, in this approach?
    – VarunC
    Commented Jan 10, 2017 at 14:20
11

In 2015, Raja Rao wrote a Salesforce Developer Relations blog post explaining how you could host a VisualForce page in an iFrame and embed that in a Lightning Component, while passing messages back and forth using HTML5 postMessage.

enter image description here

Christophe Coenraets recently wrote a blog post explaining how to embed a VF page in an iFrame to allow communication that would otherwise be prohibited from a Lightning Component.

This is also the exact approach used by lightning:container to isolate 3rd party frameworks from a security pov. As of May 2017 that technology is still in Developer Preview (DE orgs and special permission only). A good comparison of LockerService and lightning:container was also given by Christophe Coenraets.

8

Three things:

  1. There's no $Api global variable in Lightning Components today. So, {! $Api.whatever } will always fail.

  2. This is intentional at this time. There's no official/supported way to get an API-capable session ID in Lightning Components. Again, this is by design.

  3. With only a little creativity and cleverness you can create a bare-bones Visualforce page with {! $Api.SessionID }, and then call getContent(thatPage) from Apex to get a page that contains an API-capable session ID, which you should be able to parse out easily enough.

    From there, it's not hard to pass that session ID along to Lightning Components via an @AuraEnabled method.

    But let's be clear: this is a hack, the performance will be poor, and there's good reasons to keep an API-capable session ID out of your Lightning Components code.

so,Just create a simple Visualforce Page as:

<apex:page>
    Start_Of_Session_Id{!$Api.Session_ID}End_Of_Session_Id
</apex:page>

And then get the Session Id with the visualforce_Page.getContent() method, something like:

PageReference newPage = page.pagename
string sessionId = getSessionIdFromVFPage(newPage);

Where

public static String getSessionIdFromVFPage(PageReference visualforcePage){
        String content = visualforcePage.getContent().toString();
        Integer s = content.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length(),
                e = content.indexOf('End_Of_Session_Id');
        return content.substring(s, e);
    }
2

Start by going to Setup → CSP Trusted Sites and adding your trusted site.

Which in this example is: https://api.postcodes.io/

({
    postcodeSearch : function(component, postcode) {
        var url = 'https://api.postcodes.io/postcodes/' + postcode;
        this.makeAjaxRequest(component, url);
    },
    callAjax : function(method, url, async, callback) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange = function(component) {
            if (xmlhttp.readyState == 4 ) {
                callback.call(this, xmlhttp);
            }
        };

        xmlhttp.open(method, url, async);
        xmlhttp.send();
    },
    makeAjaxRequest : function(component, url) {
        this.callAjax("GET", url, true,
                      function(xmlhttp){
                          if (xmlhttp.status == 200) {
                              console.log(xmlhttp.responseText);
                          }
                          else if (xmlhttp.status == 400) {
                              console.log("makeAjaxRequest: 400 Error");
                          }else {
                              console.log("makeAjaxRequest: Error");
                          }
                      }
                     );
    }
})

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .