This article is relevant if you are programming NetSuite and you need to output JSON data to a client but you are having security problems due to the “Same Orgin Policy” (SOP).
Background
Inspired by work that Joe Son has done to show off JSONP in NetSuite, I thought it would be good to break it down into simpler terms to aid understanding of the process. In addition, there are many people developing web sites independent of NetSuite and yet need data that resides in the NetSuite platform.
This article provides the basic plumbing and includes a working script so that you can build your own code template that should work on any web site (Magento, WordPress, Drupal, Joomla, or plain HTML).
By the way, Joe Son runs the “Knowledge Share Care” web site. He and I have many conversations and we enjoy helping each other master the NetSuite development environment.
Cross-site Scripting Security Challenges
In order to produce a more secure web browsing experience, all web browsers have a mechanism to prevent foreign servers from injecting JavaScript into the user environment. Often referenced as XSS or SOP, you can learn more about it on WikiPedia.
However, many advanced sites that offer application programming interfaces (API) want to expose data and logic capacities to third parties. The browser’s security context prevented full access to these third party sites. As a result, a concept called JSONP (JSON with Padding) was developed that exploits an open area of the browser for third party scripts. You can learn more about JSONP on the WikiPedia.
Sample JSONP Code for NetSuite Suitelets
You are welcome to download the HTML file: suitelet-jsonp-example which contains a working JSONP example. We developed this against a development NetSuite account. Let me know if it stops working.
Here is the code in view format.
<!DOCTYPE html> <html> <head> <body> <a href="https://www.prolecto.com/services/innovations/"> <img src="https://www.prolecto.com/assets/media-kit/prolecto-logo--color.png" border="0"></a> <h1>Sample NetSuite Client Side JSONP created by <a href="https://www.prolecto.com/about-us/marty-zigman/"> Marty Zigman</a> @ Prolecto Resources, Inc.</h1> <a href="https://www.prolecto.com/services/innovations/"> <img src="https://www.prolecto.com/assets/media-kit/prolecto-logo--white.png" border="0"></a> <h1><a href="https://www.prolecto.com/services/innovations/">Contact Us</a> if you want help developing your specialized services.</h1> View HTML Source for Script Code <script> // typical NetSuite Suitelet URL that is available publicly (anonymous) via your deployment var u = "https://forms.na1.netsuite.com/app/site/hosting/scriptlet.nl?script=122&deploy=1&compid=TSTDRV1030358&h=44d4e730edd613155036"; // name of the call back function that drives the JSONP mechanism; this string must be the same // as your function that will ultimately process your request. cb = 'myCallBack' //here is the function that finally gets the data and where you do the work you really care about function myCallBack(data){ // place your code here to do the work // sample processing debug statements var s = 'retrieved this data from suitelet: '; console.log(s + data); alert (s + data); } //This function creates a script in the browser environment //by making our Suitelet look like something we would call in a script source tag. //It calls our SuiteLet and which returns the data. When the data returns, it must //come back as JavaScript so it can be further processed via our callback. See the sample //server side Suitelet code to understand the formatting function getJSONP(url) { // created by Marty Zigman @ Prolecto Resources, Inc. var script = document.createElement('script'); // create a DOM script element // find the head portion of the document so we can attach our script var head = document.getElementsByTagName('head')[0] || document.documentElement; // add provide the URL to the dynamic script tag script.src = url //add the script to the head tag which will then fire it head.appendChild(script); } // Now that the client side functions are setup, we need to call our function to do the work // Our Server Side SuiteLet is going to expect a parameter called "callback" to know to treat the call as "JSONP". // we must format the url to have the call back parameter getJSONP(u + '&callback=' + cb); </script> <!-- //this code is here for convenience and does NOT run in the browser. It is SuiteLet code that runs on the //NetSuite server. It should NOT be executing on the client. The assumption is that you know how to get //create Suitelets and deploy them on NetSuite function getLastPrice(request, response){ //this is the business function that gets the data; your Suitelet needs to do work to return back JSON function getPrice(){ try { var price = new btcPrice(); price.getLast(); return price.toJSON(); } catch (e) { if (e instanceof nlobjError) { nlapiLogExecution('ERROR', 'Error Result', e.getCode() + " : " + e.getDetails()); return {'result':CONST_RESULT_ERROR, 'error': e.getDetails()}; } else { nlapiLogExecution('ERROR', 'Error Result', 'Unexpected error : ' + e.toString()); return {'result':CONST_RESULT_ERROR, 'error': e.getDetails()}; }; }; }; var result = getPrice(); nlapiLogExecution('DEBUG', 'result getLastPrice', JSON.stringify(result)); // we now have the data. But we are not sure how to return it (JSON or JSONP). // see if this is a JSONP request for Same Origin Policy by supporting // a parameter from the client called "callback". If it is there, then they // want JSONP var callBackFuncStr = request.getParameter('callback'); // we found the parameter, they want JSONP if (callBackFuncStr != null){ //simply wrap the data into a javascript function call that will execute on the client //as soon as it is returned; remember, your client side function has to be there named //appropriate to receive the result var strJson = callBackFuncStr+'(\''+JSON.stringify(result)+'\');'; nlapiLogExecution('DEBUG', 'JSONP Offer', strJson); response.setContentType('JAVASCRIPT') response.write(strJson); } else { //basic JSON request; works great in server-to-server implementations //return basic JSON without concern for Same Orgin Policy response.setContentType('JAVASCRIPT') response.write(JSON.stringify(result)); }; --> </body> </html>
GET vs. POST Considerations
You may notice that the example provided is not doing a POST. JSONP does not readily support making POST events. While there are some new capacities opening for Cross Origin Resource Sharing, they are not supported on the NetSuite platform. This means that you are limited to GET calls which effectively are putting parameters on the URL. Remember there are limitations to the amount of information you can put on the URL.
If you use the popular JQuery tool to produce a $.post() call, JQuery will behind the scenes change the POST to a GET. While there is some debate, readers may want to read more information here.
JQuery Syntax for NetSuite JSONP
While the code example above is in native JavaScript, many readers are likely using JQuery to create their client side HTML pages to reference a NetSuite JSONP Suitelet. Remember that JSONP only works with a HTTP GET. If you want to send data to the server, craft it as JSON. Use the following syntax:
//produce the JSON payload; JQuery will automatically add it to the URL for a JSONP call. var data = {id: '24567', user: '23455'}; //note JQuery wants to see "callback=?" at the end of the URL; it tells the .getJSON call that this JSON request is of type JSONP var url = 'https://forms.netsuite.com/app/site/hosting/scriptlet.nl?script=23&deploy=1&compid=434534&h=88603ee219c4dc2d8b56&callback=?'; //finally, the call. For convenience, we sent the data to our custom myCallBack function -- yet not mandatory in this implementation; the call back is in // already referenced via the .done() method. Other JQuery capacities are can take care of failed calls
$.getJSON(url, data) .done(function(data){ myCallBack(data) })
Conclusion
If you are ready to unlock the potential in your NetSuite platform, contact us. We love to innovate on the platform.
The only problem I have with JSONP and the use of Suitelets is the fact that your Suitelet has to be available anonymously. If they allowed for RESTlets to be used cross domain that would open up a whole array of uses.
Hi Corey,
I agree.
I believe the Restlets were designed with the Cross Origin Sharing technique in mind where you pass in headers for authentication. Yet, I don’t believe that’s effective on public web sites where you don’t want to expose a credential.
Marty
Negative. CORS is not allowed.
Access-Control-Allow-Origin: none
I believe. I have brought it up in the last two roadmap sessions to Elham at SuiteWorld.
I was pursuing AJAX calls from mobile apps for a while, where having your credentials in the code woutldn’t be a problem as it was all compiled into native using Phonegap build.
That was just for yucks, though. I was really wanting to use them from either Portlets or Suitelets. Since forms. system. and rest. are different domains, you can’t. You can get away with using JSONP and a Suitelet as a work around. And it works great, I just don’t like the idea of security through obscurity.
Thanks Corey,
Yes, I was suspecting that CORS may have an offer. Thank you for the clarification. I don’t mind the compiled credential in the mobile app so long as everything is SSL (should be).
During SuiteWorld 2013, there was some conversation about moving these SuiteLets to the new SSPs (.ss) mechanisms. But there has been little guidance. I do want to understand that option however.
Marty
I would love to be able to explore more with the SSP stuff. It kind of reminds me of Classic ASP with some MVC thrown in. I haven’t built anything yet using it, but I would like to, at some point.
Hi Marty,
I have a question…
I developed a Suitelet Form available externally, and used a RESTlet to get the information from the form to create a record.
In order for the RESTlet to be intialize we must pass credentials, etc in the Suitelet… is this a security risk?
Can the credentials some how be exposed?
Thanks,
David
Hi David,
Restlets need the credential passed as part of the http headers in the request. Because of this, they should only be used in server side applications where you can secure the log in information. See below for code on how to construct the restlet credential:
String authorization = “NLAuth nlauth_account=” + account + “, nlauth_email=” + email + “, nlauth_signature=”+password+”, nlauth_role=”+role+””;
post.setHeader( “Authorization”, authorization );
post.setHeader( “Content-Type”, “application/json” );
post.setHeader( “Accept”, “*/*” );
I am a little confused by your implementation, but can you have your Suitelet post to NetSuite and then make another call to the Restlet? I assume the security context of the Suitelet prevents using straight SuiteScript to get at your logic.
Marty
Hi Marty,
I must have confused you with the last post.
My implementation works, I just wanted to know if there were any security risks.
Here’s the logic behind the implementation:
1. Create Suitelet Form
2. The deployment of the Suitelet form will be available without login – accessible outside NetSuite through External URL
3. Create RESTlet to create record based on Suitlet Form input
3a. Suitlets available without login have limited API’s – RESTlet allows the use of nlapiCreateRecord
4. Suitelet Form contains header information and credentials to be passed to RESTlet, as well as the JSON string data (form input)
Are there any risks with the Suitelet form, which is available without login (avaible via external URL), containing the login credentials?
Hi David,
I like this implementation. It should work. Consider that there are multiple http calls which will be slower. You may want to use deployment parameters to keep the credentials outside the script and more configurable.
As far as risks, there may be some. But I am not sure. NetSuite probably would not endorse this practice. I think they have probably regretted ever offering “Available without Login” SuiteLet capacities. One way they work on these concerns is to avoid giving you the “Built for NetSuite” certification when you are attempting to work in the SuiteCloud Developer Network program.
Marty
I’m really interested in using this method to get a user’s cart data from outside the netsuite.com domain. Possible?
We’re running into an issue where users’ cart data isn’t available to suitescripts, but that seems crazy. Why wouldn’t you expose that data? Is there NO way to get cart data from outside netsuite.com? We’ve also tried sending the request through a proxy, but then it’s not possible to get the user’s cart ID to send as part of the request headers.
Hi Eric,
I think I need more context. Can you lay down a client side script into the web site? if so, can you make a call to the server environment via this JSONP approach? If you are using the Site Builder platform, there is no real server side capacity. But if you are hitting SuiteCommerce Advanced, you can produce server side calls.
Marty
Can you share example where the input format to RESTlet is XML/SOAP?
Hi Mitsha,
I am a little confused by your request. Are you trying to call XML/Soap Services from within the REST Tier?
Is there a unique ID for shopping carts that we can access for use in an API call?
When a user adds an item to the cart we would like to send the cart’s ID in the API call so we can track abandoned carts.
Hi Marty ,
Thank you for you helpful articles. I have a question. I have an MVC application with Login using Netsuite Credentials (Suitetalk) and through the app i perform search, insert , and update. Now we need to open this application directly from netsuite customer page through a button. Once the MVC application is launched, it should not ask for the user credentials since they already logged into netsuite. How do I do this ? any help is greatly appreciated . Thanks
Hi Nada,
This may be tricky to solve and I am not sure I fully understand your situation. A diagram would help… 🙂 We have done some work with the JSession cookie that is laid down on the client during login. But now that there is token based authentication, my instinct is to see if this can help.
Marty
I have an integration with ShipStation where Shipstation makes a GET request to a Suitelet (ENDPOINT) and the Suitelet internally calls (with oAuth) a Restlet to get the data. This works fine.
The issue is, Shipstation is supposed to send the response (the shipping details like Tracking number etc.) to the same ENDPOINT, as “POST”. It fails with “405 Method not allowed” error.
I have checked the deployment, Available without login, Run as Administrator , Event Type (tested with alloptions: BLANK, GET request, POST request). No Success
After readign this post, I added &callback=? at the end of the endpoint.
Nothing works.
Can you suggest anything ?
Hello Manoj,
You may want to consider this article:
https://blog.prolecto.com/2014/09/14/diagnose-and-inspect-netsuite-https-posts/
Marty
Manoj,
I’m not sure if you’ve already found a solution to your issue, but I encountered that message when I setup my OAuth suitelet as well. The fix that worked for me was adding a header to my request for User-Agent:Mozilla/5.0
Michael: Yes I used the same. Actually, Shipstation doesn’t Allow to modify the header, so I ended up introducing an intermediate script which transforms the Response and request by adding this. So I am good for now. Thanks
Hello Marty,
Can you share with me the code of this, I already sent you a message.
I am a newbie and learning still these technologies.
Thanks. We are following up via a private conversation.
Marty
Hey,
I am running into trouble similar to the above article when trying to load a file in an iframe of a suitelet. I’m getting the error that “Blocked a frame with origin… from accessing a cross-origin frame”.
Any ideas?