XHR in Practice

Cross-Origin and Same-Origin Policy

Web browsers nearly universally prevent web pages from fetching and executing scripts on foreign domains.

The same-origin policy specifically permits scripts to run on pages that originate from the same site. Our browser identifies a page with the same origin by comparing the scheme, hostname, and port number of both pages. There are heavy run restrictions on any other interactions with scripts originating from off-site.

The Cross Origin Resource Sharing (or CORS, for short) is often a source of headaches for fetching data over XHR and dealing with foreign sources.

Fortunately, there are several ways for us to get data that is exposed by external data sources into our app. We’ll look at two of these methods and mention a third (that requires a bit more backend support):

JSONP

JSONP is a way to get past the browser security issues that are present when we’re trying to request data from a foreign domain. In order to work with JSONP, the server must be able to support the method.

JSONP works by issuing a GET request using a <script> tag, instead of using XHR requests. The JSONP technique creates a <script> tag and places it in the DOM. When it shows up in the DOM, the browser takes over and requests the script referenced in the src tag.

When the server returns the request, it surrounds the response with a JavaScript function invocation that corresponds to a request about which our JavaScript knows.

Angular provides a helper for JSONP requests using the $http service. The jsonp method of request through the $http service looks like:

$http
.jsonp("https://api.github.com?callback=JSON_CALLBACK")
.success(function(data) {
  // Data
});

When we make this call, Angular places a <script> tag on the DOM that might look something like:

<script src="https://api.github.com?callback=angular.callbacks._0" 
  type="text/javascript"></script>

Notice that Angular has replaced the JSON_CALLBACK with a custom function that Angular creates specifically for this request.

When the data comes back from the JSONP-enabled server, it is wrapped in the anonymous function automatically generated by Angular angular.callbacks._0.

In this case, the GitHub server will return some JSON wrapped in the callback, and its response might look like:

// shortened for brevity
angular.callbacks._0({
  "meta": {
    "X-RateLimit-Limit": "60",
    "status": 200,
  },
  "data": {
    "current_user_url": "https://api.github.com/user"
  }
})

When Angular calls the special function, it resolves the $http promise.

When we write our own back-end servers to support JSONP, we need to ensure that, when we respond, we wrap the data inside the function given by the request with callback.

When using JSONP, we need to be aware of the potential security risks. First, we’re opening up our server to allow a back-end server to call any JavaScript in our app.

A foreign site that we do not control can change its script at any time (or a malicious cracker could), exposing our site for vulnerabilities. The server or a middleman could potentially send extra JavaScript logic back into our page that could expose private user data.

We can only use JSONP to send GET requests, since we’re setting a GET request in the <script> tag. Additionally, it’s tough to manage errors on a script tag. We should use JSONP sparingly and only with servers we trust and control.

Using CORS

In recent years, the W3C has created the CORS specification, or Cross Origin Resource Sharing policy, to replace the JSONP hack in a standard way.

The CORS specification is simply an extension to the standard XMLHttpRequest object that allows JavaScript to make cross-domain XHR calls. It does so by preflighting a request to the server to effectively ask for permission to send the request.

This preflight gives the receiving server the ability to accept or reject any request from all servers, a select server, or set of servers. That means that both the client app and the server app need to coordinate to provide data to the client/server.

The W3C wrote the CORS specification with the intention of abstracting away many of the details from the client-side developer so that it appears as though the request is made in the same way as a same-origin request.

Configuration

To use CORS within Angular, we need to tell Angular that we’re using CORS. We use the .config() method on our Angular app module to set two options.

First, we need to tell Angular to use the XDomain, and we must remove the X-Requested-With header from all of our requests.

The X-Requested-With header has been removed from the common header defaults, but it’s a good idea to ensure it’s been removed anyway.

angular.module('myApp', [])
.config(function($httpProvider) {
  $httpProvider.defaults.useXDomain = true;
  delete $httpProvider.defaults.headers
    .common['X-Requested-With'];
});

Now we’re ready to make CORS requests.

Server CORS Requirements

Although we will not dive into server-side CORS setup in this chapter (we do in the server communication chapter), it’s important that the server we’re working with support CORS.

A server supporting CORS must respond to requests with several access control headers:

The value of this header must either echo the origin request header or be a * to allow any and all requests from any origin.

By default, CORS requests are not made with cookies. If the server includes this header, then we can send cookies along with our request by setting the withCredentials option to true.

If we set the withCredentials option in our $http request to true, but the server does not respond with this header, then the request will fail and vice versa.

The back-end server must also be able to handle OPTIONS request methods.

There are two types of CORS requests: simple and non-simple.

Simple Requests

Requests are simple if they match one of these HTTP methods:

and if they are made with one or many of the following HTTP headers, and no others:

We categorize these as simple requests because the browser can make these types of requests without the use of CORS. Simple requests do NOT require any special type of communication between the server and the client.

A simple CORS request using the $http service looks like any other request:

$http
.get("https://api.github.com")
.success(function(data) {
  // Data
});

Non-Simple Requests

Non-simple requests are those that violate the requirements for the simple requests. If we want to support PUT or DELETE methods, or if we want to set the specific type of content type in our requests, then we’re going to call a non-simple request.

Although, as client-side developers, this request doesn’t look any different to us, the browser handles the request differently.

The browser actually sends two requests: the preflight and the request. First, the browser issues a preflight request wherein the server grants (or denies) permission to make the request. If the permissions have been granted, then the browser will make the actual request.

The browser takes care of handling the CORS request transparently.

Similar to the simple request, the browser will add the Origin header to both of the requests (preflight and the actual request).

Preflight Request

The browser makes the preflight request as an OPTIONS request. It contains a few headers in the request:

This header is the HTTP method of the actual request. It is always included in the request.

This header is a comma-delimited list of non-simple headers that are included in the request.

The server should accept the request, then we must check if the HTTP method and the headers are valid. If they are, the server should respond with the following headers:

The value of this header must either echo the origin request header or be a * to allow any and all requests from any origin.

This list of allowed HTTP methods is helpful, as we can cache the request in the client, and we don’t have to constantly ask for preflights in future requests.

If the Access-Control-Request-Headers header is set, then the server should respond with this header.

We expect the server to respond with a 200 response status code if the request is acceptable. If it is, then the second request will be made.

CORS is not a security mechanism; it is simply a standard that modern browsers implement. It’s still our responsibility to set up security in our app.

Non-simple requests look exactly like regular requests inside Angular:

$http
.delete("https://api.github.com/api/users/1")
.success(function(data) {
  // Data
});

Server-Side Proxies

The simplest method for making requests to any server, however, is to simply use a back-end server on the same domain (or on a remote server with CORS setup) as a proxy for remote resources.

Rather than making requests to foreign resources through our client-side app, we can simply use our own local server to make and respond to requests for our client-side app.

In this way, we enable older browsers to make requests (only modern browsers implement CORS), without requiring a second request for non-simple CORS requests, and we can use standard browser-level security as it was intended to be used.

In order to use a server-side proxy, we need to set up a local server to handle our requests, which takes care of sending the actual requests.

For more information about setting up a server-side component, read the Server communication chapter.

Working with JSON

 
This page is a preview of ng-book.
Get the rest of this chapter plus 600 pages of the best Angular content on the web.

 

Ready to master AngularJS?

  • What if you could master the entire framework – with solid foundations – in less time without beating your head against a wall? Imagine how quickly you could work if you knew the best practices and the best tools?
  • Stop wasting your time searching and have everything you need to be productive in one, well-organized place, with complete examples to get your project up without needing to resort to endless hours of research.
  • You will learn what you need to know to work professionally with ng-book: The Complete Book on AngularJS or get your money back.
Get it now