JSONRequest

Douglas Crockford
douglas@crockford.com
2006-04-17 (Updated 2012-08-10)

Abstract

XMLHttpRequest has a security model that is inadequate for supporting the next generation of web applications. JSONRequest is proposed as a new browser service that allows for two-way data exchange with any JSON data server without exposing users or organization to harm. It exchanges data between scripts on pages with JSON servers in the web. It is hoped that browser makers will build this feature into their products in order to enable the next advance in web application development.

Motivation

The next generation of web applications will be much more data intensive. They will want to go to a server, any server, and exchange data. The XMLHttpRequest interface suggests, but does not achieve, this. It is severely limited by a defective security model.

XMLHttpRequest is constrained by the Same Origin Policy. This constrains the interface to only connect with the server that delivered the base page. This rule deals with some common, long-standing security flaws in web architecture.

If the Same Origin Policy were not in place, then the user could be harmed by XSS (cross site scripting) attacks. In the following examples, we will have a page that was created by miscreants at pirate.net. That page will attempt to compromise the user's relationship with penzance.org.

If the Same Origin Policy were not in effect, a pirate.net page could send a request via XMLHttpRequest to penzance.org. That request would carry the penzance.org cookies. If penzance.org were using cookies for authentication, then it would be tricked into acting on the request as though it had been initiated by the user. Any request to a site will carry the cookies associated with the site. This would allow pirate.net the right to use penzance.org cookies.

If penzance.org sits behind a firewall, and if the internal servers assume that the firewall makes explicit authorization unnecessary, then the pirate.net page could be used as a proxy, accessing the contents of penzance.org for transmission back to pirate.net. This would be possible because XMLHttpRequest can obtain XML-like data (such as HTML documents) as well as non-XML text.

The Same Origin Policy frustrates these attacks, but it also frustrates a larger class of legitimate uses. It should be possible for a script in a page to access data from other servers without compromising the security of the user or his organization.

Surprisingly, the Same Origin Policy does not apply to scripts. So some developers have begun to dynamically generate <script> tags to connect to any server. The server sends back a script which delivers some data. Unfortunately, the script runs with the same authority as a scripts from the originating page, allowing the script to steal cookies or directly access the originating server. This is unsafe. If a penzance.org page loaded a script from pirate.net, the script could damage the relationship between penzance.org and its users by stealing cookies and making requests of the penzance.org server.

This document proposes a safe, reliable data service which will allow a script on any page to connect to any server and exchange data. It would make it possible for a page from pirate.net to access data from any server without compromising penzance.org, and for penzance.org to access pirate.net data on its pages without compromising its own users.

JSON

JSON is a data interchange format which is based on a safe subset of JavaScript. JSON can represent simple or complex structured data. JSON cannot represent functions or expressions. It is strictly data. It has very specific rules of syntax, so it is very straightforward to determine that a JSON text is syntactically well formed. A JSON text can easily be converted into a JavaScript value, which makes it a very convenient format for use with JavaScript. There is support for use of JSON with many other languages, including C#, Java, Perl, PHP, Python, and Ruby. More information on JSON can be found at www.JSON.org.

JSON does not look like XML, so HTML text fed to a JSON parser will produce an error.

JSONRequest

JSONRequest is a global JavaScript object. It provides three methods: post, get, and cancel.

JSONRequest.post

JSONRequest.post does an HTTP POST of the serialization of a JavaScript object or array, gets the response, and parses the response into a JavaScript value. If the parse is successful, it returns the value to the requesting script. In making the request, no HTTP authentication or cookies are sent. Any cookies returned by the server cause the request to fail. The JSONRequest service can only be used to send and receive JSON-encoded values. JSONRequest cannot be used to retrieve other text formats.

JSONRequest.post takes four parameters:

parameter type description
url string The URL to POST to. The URL does not need to be related to the page's URL.
send object The JavaScript object or array to send as the POST data. It will be serialized as JSON text. Cyclical structures will fail.
done function (requestNumber, value, exception) The function to be called when the request is completed. If the request was successful, the function will receive the request number and the returned value. If it is not successful, it will receive the request number and an exception object. The done function will not be called until after the call to JSONRequest returns a serial number.
timeout number The number of milliseconds to wait for the response. This parameter is optional. The default is 10000 (10 seconds).

JSONRequest.post returns a serial number if the request parameters are acceptable. It throws a JSONRequestError exception if the request is rejected. The request will be rejected if

The request number may be used by a script to match requests with responses. It is provided as a convenience for programmers who are not comfortable with the use of function values and closures, and for request cancellation.

Example:

requestNumber = JSONRequest.post(
    "https://json.penzance.org/request",
    {
        user: "doctoravatar@yahoo.com",
        t: "vlIj",
        zip: 94089,
        forecast: 7
    },
    function (requestNumber, value, exception) {
        if (value) {
            processResponse(value);
        } else {
            processError(exception);
        }
    }
); 

After JSONRequest.post has verified the parameters, it will queue the request and return the request number. The done function value will be invoked later when the outcome of the request is known.

No cookies or implicit authentication information are sent with the POST operation. Any authentication information must be placed in the send data or in the url. The JSON text that was serialized from the send data is used as the body of the request. The character encoding is UTF-8. An implementation may choose to gzip the JSON text.

The request may use either http or https. This choice is independent of the security of the page.

POST /request HTTP/1.1
Accept: application/jsonrequest
Content-Encoding: identity
Content-Length: 72
Content-Type: application/jsonrequest
Host: json.penzance.org

{"user":"doctoravatar@penzance.com","forecast":7,"t":"vlIj","zip":94089}

After the server has acknowledged the request, it has until the time limit expires to produce a response. If the time limit is exceeded, or if the connection is closed before a complete response is sent, then the request fails. If the HTTP status code is not 200 OK, then the request fails.

HTTP/1.1 200 OK
Content-Type: application/jsonrequest
Content-Length: xxxx

The body of the response is a JSON text, encoded in UTF-8. If the text contains any JSON encoding errors, then the request fails.

If the request succeeds, then the done function is called with the request number and the value obtained from the parsing of the JSON text. If the request fails, then the done function is called with the request number and an exception object, indicating the communication failure. If a server wishes to communicate an application-level error, then it should return it as a JSON text with an HTTP status code of 200 OK.

The browser must be able to keep open at least two requests per host per page. Excess requests may be queued. The browser should attempt to keep alive connections. These connections are counted separately from the connections that the browser uses to fetch HTML and related resources.

JSONRequest.get

JSONRequest.get does an HTTP GET request, gets the response, and parses the response into a JavaScript value. If the parse is successful, it returns the value to the requesting script. In making the request, no HTTP authentication or cookies are sent. Any cookies returned by the server cause the request to fail. The JSONRequest.get service can only be used to obtain JSON-encoded values. JSONRequest.get cannot be used to retrieve other text formats.

JSONRequest.get takes three parameters:

parameter type description
url string The URL to GET from. The URL does not need to be related to the page's URL.
done function (requestNumber, value, exception) The function to be called when the request is completed. If the request was successful, the function will receive the request number and the returned value. If it is not successful, it will receive the request number and an exception object. The done function will not be called until after the call to JSONRequest returns a serial number.
timeout number The number of milliseconds to wait for the response. This parameter is optional. The default is 10000 (10 seconds).

JSONRequest.get returns a serial number if the request parameters are acceptable. It throws a JSONRequestError exception if the request is rejected. The request will be rejected if

The request number can be used by a script to match requests with responses.

Example:

requestNumber = JSONRequest.get(
    "https://json.penzance.org/request",
    function (requestNumber, value, exception) {
        if (value) {
            processResponse(value);
        } else {
            processError(exception);
        }
    }
); 

After JSONRequest.get has verified the parameters, it will queue the request and return the request number. The done function value will be invoked later when the outcome of the request is known.

No cookies or implicit authentication information is sent with the GET operation. Any authentication information must be placed in the url.

The request may use either http or https. This choice is independent of the security of the page.

GET /request HTTP/1.1
Accept: application/jsonrequest
Host: json.penzance.org

After the server has acknowledged the request, it has until the timeout interval expires to produce a response. If the time limit is exceeded, or if the connection is closed before a complete response is sent, then the request fails.

HTTP/1.1 200 OK
Content-Type: application/jsonrequest
Content-Length: xxxx

The body of the response is a JSON text, encoded in UTF-8. If the text contains any JSON encoding errors, then the request fails.

If the request succeeds, then the done function is called with the request number and the value obtained from the parsing of the JSON text. If the request fails, then the done function is called with the request number and an exception object, indicating the communication failure. If a server wishes to communicate an application-level error, then it should return it as a JSON text with an HTTP status code of 200 OK.

An implementation may cache the results of GET requests.

JSONRequest.clear

A document can be removed from the GET cache by calling JSONRequest.clear with its url. Nothing is returned. It is not possible to determine with this function if the document had ever been in the cache.

JSONRequest.clear(url);

JSONRequest.cancel

A request can be canceled by calling JSONRequest.cancel with the request number as the only parameter. Nothing is returned. There is no guarantee that the request will not be sent to the server since it is possible that it had been transmitted before the cancel request was made.

JSONRequest.cancel(requestNumber);

If the request is still in the outgoing message queue, it will be deleted from the queue.

If the request is in progress, an attempt will be made to abort it.

If the request cannot be found, then the cancellation will be ignored.

When a message is successfully canceled, the done callback function of the request is called with an exception message of "canceled".

It is possible that from the server's point of view, the transaction was completed normally, but in the client's point of view was canceled.

HTTP Header Fields

Accept

The only accept type used with JSONRequest is application/jsonrequest. The use of this unique type prevents JSONRequest from interacting with legacy systems that assumed that a firewall was sufficient to protect them from unintended web access.

Content-Type

The only content type used with JSONRequest is application/jsonrequest.

Content-Encoding

The content encoding can be identity (the default) or gzip.

Exceptions

Exceptions can be produced either when the JSONRequest function is called, or when the done callback function is invoked. An exception object contains a name member whose value will always be the string "JSONRequestError", and a message member, which contains a string that explains the error.

{name: "JSONRequestError", message: "error message"}

These are the messages that can be produced.

message meaning
"bad URL" The URL was not formatted correctly and could not be used to make a request.
"bad data" The send data was not an object or array, or was cyclical, or was too big.
"bad function" The callback function was not a function with an arity of 3.
"bad timeout" The timeout parameter is not a positive integer.
"not ok" The server supplied a response that was not 200 OK.
"no response" The server did not respond, or a timeout occurred.
"bad response" The response was not a valid JSON text, or had unexpected material in the HTTP response header.
"canceled" The response was canceled.

Delay

When a request fails (delivering an exception object with the message "not ok", "no response", or "bad response" to a done callback function), then a delay will be added to the dispatching of all subsequent requests. This is intended to frustrate denial of service attacks, timing analysis attacks, and exhaustive search attacks.

Each failure increases the current delay value by 500 milliseconds plus a random number of milliseconds (between 0 and 511). Each successful request reduces the delay by 10 milliseconds until the delay goes to zero. Each page keeps its own delay value.

Canceled requests increase the delay by 20 milliseconds.

Security

The JSONRequest has some features that allow it to be exempted from the Same Origin Policy.

  1. JSONRequest does not send or receive cookies or passwords in HTTP headers. This avoids false authorization situations. Knowing the name of a site does not grant the ability to use its browser credentials.
  2. JSONRequest works only with JSON text. The JSONRequest cannot be used to access legacy data or documents or scripts. This avoids attacks on internal websites which assume that access is sufficient authorization. A request will fail if the response is not perfectly UTF-8 encoded. Suboptimal aliases and surrogates will fail. A request will fail if the response is not strictly in JSON format. A request will fail if the server does not respond to POST with a JSON payload.
  3. Reponses will be rejected unless they contain a JSONRequest content type. This makes it impossible to use JSONRequest to obtain data from insecure legacy servers.
  4. JSONRequest reveals very little error information. In some cases, the goal of a miscreant is to access the information that can be obtained from an error message. JSONRequest does not return this information to the requesting script. It may provide the information to the user through a log or other mechanism, but not in a form that the script can ordinarily access.
  5. JSONRequest accumulates random delays before acting on new requests when previous requests have failed. This is to frustrate timing analysis attacks and denial of service attacks.

The JSONRequest does only one thing: It exchanges data between scripts on pages with JSON servers in the web. It provides this highly valuable service while introducing no new security vulnerabilities.

A browser within a filewall may have the capability to interact with a server (penzance.org). Computers on the outside do not have that capability. Can a computer on the outside (pirate.net) cause a browser to act as its agent in interacting with an internal server?

Current, XMLHttpRequest does not allow a script from a page from pirate.net to connect to penzance.org because of the Same Origin Policy.

JSONRequest does allow the connection, but with some limitations:

But what of legacy applications that accept POST. Could JSONRequest be used to improperly POST to these applications, thereby corrupting databases? JSONRequest mitigates this danger because Cookies and HTTP authentication are not sent.

Contrast this to form.submit, which can send a POST body and cookies and HTTP authentication. JSONRequest is more secure than the form.submit feature which is currently implemented everywhere. By switching to a policy of responding only to well-formatted JSONRequest, applications can be made more secure.

Duplex

JSONRequest is designed to support duplex connections. This permits applications in which the server can asynchronously initiate transmissions. This is done by using two simultaneous requests: one to send and the other to receive. By using the timeout parameter, a POST request can be left pending until the server determines that it has timely data to send.

Duplex connections can be used in realtime notification applications such as process management and finance. It can also be used in collaborative applications such as instant messaging, instant email, chat, games, presentation, and shared applications.