Skip to content

Instantly share code, notes, and snippets.

@GAKINDUSTRIES
Last active March 6, 2018 05:00
Show Gist options
  • Save GAKINDUSTRIES/8b0072a90c7bd777735ebfd0d57f255b to your computer and use it in GitHub Desktop.
Save GAKINDUSTRIES/8b0072a90c7bd777735ebfd0d57f255b to your computer and use it in GitHub Desktop.
Let's talk about JSONP.md

How do I first know JSONP

When I first start working with iframes that belongs to a third part application, it comes to my mind the possibility to manipulate them. My first stone in the road Same-origin-policy. If the iframe comes from a different domain, a browser's cross-domain policy would kick in, preventing the iframe from accessing cookies, local storage, or the DOM from its embedding document.

After getting deep into Same origin policy I found that this is not just an specific issue with iframes. The problem comes when you try to change the URL to point to a different domain such as http://domain.com/users/guille. The reason this fails is because there are security implications that come with making requests to different origins. I won’t bore you with specifics, but you can read more here.

The one item that bypasses this limitation is <script> tags (as well as img, frames, iframes, etc). When you use a script tag, the domain limitation is ignored, but under normal circumstances, you can't really do anything with the results, the script just gets evaluated. The browser will, in order, download the script file, evaluate its contents, interpret the raw JSON data as a block, and throw a syntax error. Even if the data was interpreted as a JavaScript object literal, it could not be accessed by JavaScript running in the browser, since without a variable assignment, object literals are inaccessible. That's where JSONP gets into scene !!

What is and what not is JSONP

JSON (Javascript Object Notation) is a convenient way to transport data between applications, especially when the destination is a Javascript application.

{ "name": "guille", "gender": "male" }

JSONP is JSON with padding. Response is JSON data but with a function call wrappedaround it.

functioncallExample({ "name": "guille", "gender": "male" });

JSONP is a simple way to overcome browser restrictions when sending JSON responses from different domains from the client.

Before explaining how JSONP moving forward, there are major issues that should be mentioned:

  • JSONP can only be used to perform Cross-Domain GET requests.
  • The server must explicitly support JSONP requests.
  • You should have absolute trust in the server providing JSONP responses. JSONP could expose your website to a plethora of security vulnerabilities if the server is compromised.

How it works

When you make your request to a server that is JSONP enabled, you pass a special parameter that tells the server a little bit about your page. That way, the server is able to nicely wrap up its response in a way that your page can handle. For example, say the server expects a parameter called "callback" to enable its JSONP capabilities. Then your request would look like:

http://www.domain.com/api/sample?callback=mycallback

Without JSONP, this might return some basic JavaScript object, like so:

{ name: 'Super Mario Bros', description: 'Super Mario will save princess Peach' }

Using JSONP, when the server receives the "callback" parameter, it wraps up the result a little differently, returning something like this:

mycallback({name: 'Super Mario Bros', description: 'Super Mario will rescue princess Peach'});

And now, when the script is loaded, it'll be evaluated, and your function will be executed. Voila, cross-domain requests!

Many thanks to jvnema for this clear explanation.

JSONP using JQuery

Here's an example of how to implement JSOP using Jquery. In the following example we’re getting the user information for John Resig (jQuery creator) and using a logResults callback function to log the response to the console:

function logResults(json){
  console.log(json);
}

$.ajax({
  url: "https://api.example.com/users/guillermo",
  dataType: "jsonp",
  jsonpCallback: "logResults"
});

This can also be written as:

$.getJSON("https://api.example.com/users/guillermo?callback=?",function(json){
  console.log(json);
});

The ? on the end of the URL tells jQuery that it’s dealing with a JSONP request instead of JSON. jQuery then automatically registers the callback function which it calls when the request retruns.

If you’d like to learn more about jQuery’s getJSON method, check out ([here]Ajax/jQuery.getJSON).

JSONP using Rails server side

Accepting JSONP request on the server side not need to much magic. Just adding the callback to the response would be enough. Check the following example:

class UsersController < ApplicationController
  def show
    respond_to do |format|
      format.js do
        @user = User.find params[:id]
        render :json => @user, :callback => params[:callback]
      end
    end
  end
end

Drying up our controllers

If our idea is to accept JSONP in many of our requests (or maybe all of them), adding the callback to each of our responses can be tedious. To avoid that, we can use rack-jsonp-middleware ruby gem. This gem includes a piece of Rack middleware that can take care of this repetition for us. As an example see the following code:

#gemfile

gem 'rack-jsonp-middleware'

Then add it to the Rails middleware stack:

# config/application.rb

class Application < Rails::Application

  #some other staff
  config.middleware.use Rack::JSONP
end

rack-jsonp-middleware considers a request a JSONP request if the url ends in .jsonp. We’ll need to change the ending of our client url from .js to .jsonp.

Finally our controller would leave more clean:

class UsersController < ApplicationController
  respond_to :json

  def show
    @user = User.find params[:id]
    respond_with @user
  end
end

Inconveniences in using JSONP

At last when we think that we found the key to rule the world, we realized that not all are peaches and cream. There are many inconveniences when using JSONP.

First of all, the only HTTP method you can use is GET since that is the only method script tags support. This immediately eliminates the use of JSONP as an option to interact with nice RESTful APIs that use other HTTP verbs to do fun stuff like CRUD. And while we’re on the subject of GET, note that using anything other than URL parameters to communicate with the server API (e.g. sending some JSON over) is also not possible

Secondly, since you are relying on a script tag, there is no uniform way to catch errors. Some browsers will allow attaching a callback to the script tag (e.g. onError() {}), but it is not universally supported. If the called API is doing syntactically correct things like returning an HTTP 404 response code when a resource cannot be found, there is no way to easily catch that error condition or parse the response body for further information

And I left the worst for the end. Security issues. As long as you are only providing public data, the most significant risk is injection through parameters. As an example an attacker could perform a SQL injection attack against your JSONp service by manipulating the parameters to the service (if it has any).

If you are providing data that requires authentication, things get much worse. A legit site sets up a callback, includes the (dynamic) script tag to the JSON service, which - upon loading - invokes the callback, and displays the data to the user. What's stopping an attacker from setting up a webpage that defines the same callback and script tag, but instead steals the users data? This is referred to as JSON-hijacking and it's basically a Cross site request forgery (XSRF/CSRF)-attack.

Final thoughts

While it's a really powerfull tool to make request to a third part endpoint, there are several limitations and security issues you should take into account before deciding wheter to use it or not. Following jvnema suggestions:"These days (2015), CORS is the recommended approach vs. JSONRequest. JSONP is still useful for older browser support, but given the security implications, unless you have no choice CORS is the better choice."

References:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment