What is JSONP and how does it work?

Asked

Viewed 10,986 times

37

What is JSONP, how to use and why to use JSONP instead of AJAX?

  • The corrections of the spelling of @Gabrielsantos are corrections even, or are there differences between pt-PT and pt-BR? I was in doubt, because in Brazil we use "why" when asking a question and "why" when answering, and there is no "vêz" ("see" with "s" accent refers to the verb "see"), but I do not know how it is in Portugal.

  • 1

    @mgibsonbr I went to see the dictionary and vêz is actually a spelling error. O por que and porque is difference en and en.

2 answers

32


JSONP (JSON with padding) is a technique designed to circumvent a limitation of the use of Ajax in browsers: the fact that the Policy of Same Origin does not allow the domain A make an Ajax request for the domain B. Before the CORS ("cross-origin resource sharing") is proposed - and supported by the main browsers - there was no easy and safe way to circumvent this limitation, and even today support for this technique is relatively rare.

Given this limitation, a way was devised to circumvent it by taking advantage of the fact that the tag script is one of the few that is exempt from the Same Origin Policy (and so it remains for historical reasons, despite the negative impact it has on web security in general). Now, if the main interest of Ajax is to give programmatic access to a certain content, and a site can include scripts from another site without any restriction, then why not submit this content in the form of a script? Like all Javascript on the page has the same execution context (no matter its origin), the code coming from the site B can call functions defined by the website A, using these functions to communicate the content consulted.

A detailed description of the technique, with examples, can be seen in Sergio’s response. As to why to use, it is necessary to observe some points:

  • The JSONP name is inappropriate because although the returned data is referred to as "JSON" in reality it is only literal for Javascript objects, and not strings (text), which are usually included in the script. And anyway, the "combined" is that the return is only the object (the "JSON") within a function call (the "padding"), guy:

    meuCallback({"um":"objeto", "qualquer":[1,2,3]});

    But if the site wants to "escape from the combined" - for example using some other code that builds this object and passes it to the function indicated as callback - nothing stops you from doing that. And that’s relevant because...

  • A script on a page is an arbitrary code running on the user’s computer; moreover, every script has unrestricted access not only to the DOM but also to the execution context (shared by the other scripts) and also to cookies (with the exception of those marked as HttpOnly), browsing history, permanent storage (local Storage, Indexeddb, etc.) and even the ability to navigate the browser to another page of your choice (inside or outside the site), among other capabilities.

    For this reason, the first question to ask before using JSONP is whether you relies on the area consulted, or if it is your domain that will support JSONP, such as making third parties that will consume your content trust your website. Remembering that it is not enough that the content served by this domain is correct today, if in the future it has its server compromised, for example, users of all sites that consume its content by JSONP will be vulnerable to an arbitrary and undetermined attack.

    So my answer to why use is simply: only use it if there’s no other way...

  • The preferred way of making requests to different domains is even CORS. It takes extra work to set it up correctly, and older user agents may not give proper support to this technique, but as time goes on this problem becomes smaller and smaller (both by the fraction of users who use it browsers modern, as by the evolution of the platforms on the server side in order to give this support and facilitate configuration). If it is possible to use CORS, use.

  • Many websites have not been made thinking of sharing their content through an Ajax API. The use of JSONP requires server support, it is not something that can be done only on the consumer side of the information. So if this support does not currently exist, we have two main options:

    • Use your own domain as proxy for the external domain; in this case it is not necessary to use JSONP, CORS, or anything special, as your server will query the external domain as if it were a user by browser, and pass it on to the real user via ordinary Ajax. It’s not ideal, given the extra load it puts on your server, but in some circumstances it may be the only option.

    • Ask the external domain to update your system to support Ajax requests from outside your domain. Here comes the following question: if a new functionality is being implemented, to want to use the insecure approach when it is not only preferable but also much easier (since it is enough to include some headers add, no change to existing Ajax code) simply enable CORS? Again, unless it is an explicit requirement of your project to support browsers there is no reason to use JSONP.

Finally, a few things could be made to mitigate the risks of using JSONP, but nothing 100% fail-safe, and in any case the most secure solutions would require support for modern HTML5 features anyway, and a browser that supports these features will probably also support CORS. An example (Disclaimer: I’m not suggesting use this code in practice! ) would delegate to a iframe the task of making the JSONP call in an attempt to isolate the code from the rest of the page:

teste_jsonp.html

function testeJSONP(url, callback) {
    var iframe = document.createElement("iframe");
    iframe.style.display = "none";
    iframe.src = "jsonp_seguro.html";
    iframe.sandbox = "allow-scripts"; // Proíbe tudo, exceto a execução de scripts
    iframe.onload = function() {
        // Se comunica com o iframe através de postMessage
        window.addEventListener("message", function(event) {
            callback(JSON.parse(event.data));
        }, false);
        iframe.contentWindow.postMessage(url, "*");
    };
    document.body.appendChild(iframe);
}

testeJSONP('http://ip.jsontest.com/?callback=showIP', function(dados) {
    console.log(dados);
});

jsonp_safe.html

window.addEventListener("message", function(event) {
    window.showIP = function(dados) {
        // Repassa os dados recebidos para a página principal
        // (note que isso força esses dados a serem passados como string)
        event.source.postMessage(JSON.stringify(dados), "*");

        /**** E se o domínio for desonesto? E em vez de retornar um JSON: ****/

        // Tenta fazer várias coisas maliciosas
        try { var target = window.opener || top; target.location.href = "http://google.com"; } catch(e) { console.log(e); }
        try { var cookies = document.cookie; } catch(e) { console.log(e); }
        try { var teste = localStorage.getItem('teste'); } catch(e) { console.log(e); }

        // Essas ele ainda consegue... :(
        /*try { alert("SPAM!"); } catch(e) { console.log(e); }
        try { history.go(-1); } catch(e) { console.log(e); }*/
    };

    // Aqui é feita a chamada JSONP de fato; fonte: resposta do Sergio
    var script = document.createElement('script');
    script.src = event.data;
    document.body.appendChild(script);

}, false);

Example (open the console if you want to see how malicious actions have been blocked).

Reiterating: don’t try to "amend" security measures to protect JSONP, just use this technique in conjunction with websites on which you trust.

  • Very good! ++ Good that you mentioned more deeply the insecurity related to the use of JSONP

  • ssl error on your site. Certificate expired!

  • @Gabrielsantos Hehe am I aware... :P For now I walk without time to update this site, and as it is only a personal site even without anything very important I’m pushing with my stomach.

16

The JSONP is an alternative to send and receive data on old browsers, and/or websites that have CORS deactivated.

If the browser is in the domain meusite.com and we want to receive data from another domain (for example: teusite.net) many browsers will fail (older versions of IE could not make AJAX calls to different domains). The error may be because CORS is disabled, or because the browser is old and cannot order from different domains. Sites with CORS disabled do not allow data exchange via AJAX.

But there is one exception in browsers to fetch content from other domain sites. Tags <script> allow searching Javascript for other domains.
JSONP precisely explores this possibility.

If we add a script to the page the browser will run this script. So what we have to do is that the server returns something that runs.

The way he found himself was via callback in the query string script url and completed by the server. That is, the client looks for this url, for example:

http://teusite.net/?cliente=2336&produto=45&quantidade=73&callback=temp_callback

At this url above I gathered some data on query string for the server to process. I also added a parameter temp_callback which is the name I gave to a function I created just for this purpose.

The server will answer a string that the browser uses as Javascript, and now you know what the name of the function I want to use and returns me this text:

temp_callback({preco: 300});

When this is inserted in the page script, it is run and can be used in the rest of Javascript. Note that this function temp_callback must be closed to the page and the global scope for the browser to run.

Example on the client side

window.showIP = function (resposta) {
    alert('O seu IP é: ' + resposta.ip);
};

var src = 'http://ip.jsontest.com/?callback=showIP'; // este é um exemplo que funciona, mas aqui deve ser o url que queres usar
var script = document.createElement('script');
script.src = src;
document.body.appendChild(script);

jsFiddle: http://jsfiddle.net/s14totoy/

Example of server-side code:

(the server returns a string which must be valid Javascript, ie: the function name, open parentesis, what is passed to the function, close parentheses).

PHP:

$resposta = '{preco:'.$preco.'}';
echo $_REQUEST["callback_a"].'('.$resposta.')';

Nodejs (with express.js):

res.jsonp(req.query.callback + '('+ objPreco + ');');

To end and if it is an option you must use CORS instead of JSONP because an AJAX call is a more complete interface that allows different methods and gives error codes and connection status that JSONP does not allow.


Extras:

  • avoid duplicates:

Sometimes there are several JSONP requests running close to each other I usually defenir the callback so, to avoid duplicates and errors in the code:

var callbackName = 'temp_callback_' + new Date().getTime() + Math.floor(Math.random() * 100);
  • complete example:

A full version ready to use:

function jsonp(url, dados, callback) {
    function toQueryString(object) {
        return Object.keys(object).map(function (prop) {
            return prop + '=' + object[prop];
        }).join('&');
    }

    var resposta;
    var queryStr = dados ? toQueryString(dados) + '&' : '';
    var nomeDaCallback = 'temp_callback_' + new Date().getTime() + Math.floor(Math.random() * 100);
    window[nomeDaCallback] = function (res) {
        delete window[nomeDaCallback];
        document.body.removeChild(script);
        resposta = res;
    };

    var src = [url, '?', queryStr, 'callback=', nomeDaCallback].join('');
    var script = document.createElement('script');
    script.src = src;
    script.onload = function (e) {
        callback(resposta, true, e);
    }
    script.onerror = function (e) {
        callback(resposta, false, e);
    }
    document.body.appendChild(script);
}

jsFiddle: http://jsfiddle.net/kdyfq2mz/1

Browser other questions tagged

You are not signed in. Login or sign up in order to post.