How to make a cross domain request in JavaScript using CORS

10‑01‑2017 Frits van Campen 10 min.

Cross-origin resource sharing (or CORS) can be used to make AJAX requests to another domain. We'll look at how to set up CORS on the server in PHP, how to make the request in JavaScript and some considerations.

CORS as a concept is broader than just AJAX requests but this is it's main use. Here at Moxio we use cross domain requests for our single sign-on service. We will explain how to implement CORS using a single sign-on service as an example.

Single Sign-on overview

Here you can see a flow chart describing our sign-on process.

  • In 1 we request the login status of foo.app.moxio.com. We send the session cookie, the application verifies it against a list of active sessions.
  • In 2 we request the login status of sso.moxio.com. We send the session cookie, the application verifies it against a list of active sessions.
  • In 3 we request an authentication token from sso.moxio.com. We send the session cookie and the server will create an authentication token for us, the token is stored and returned.
  • In 4 we perform a login with the authentication token. This starts a session on foo.app.moxio.com. The server on foo.app.moxio.com will use the authentication token on sso.moxio.com to verify the user, this part is not shown.
  • In 5 we perform a login with username and password on sso.moxio.com. This starts a new session on sso.moxio.com.

As you can see we send requests to foo.app.moxio.com and sso.moxio.com. The user never browses away from foo.app.moxio.com so the requests to sso.moxio.com are cross domain requests.

Setting up a CORS policy

By default you are not allowed to make AJAX requests to another domain. Your browser applies the Same-origin policy as part of the web security model.

To allow the browser to make a cross domain request from foo.app.moxio.com to sso.moxio.com we must set up a CORS policy on the target domain. The CORS policy is enforced by the browser. If you don't control the target domain you wont be able to set a CORS policy, look at alternatives to CORS.

A CORS policy is a set of HTTP response headers. A basic CORS policy can look like this:

Access-Control-Allow-Origin: https://foo.app.moxio.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type 

This policy states that the origin https://foo.app.moxio.com is allowed to make a POST request, cookies may be included and we are allowed to send the Content-Type header.

The request in JavaScript

This shows you how to make a request in JavaScript that is allowed by this policy.

var http_request;
http_request = new XMLHTTPRequest();
http_request.onreadystatechange = function () { /* .. */ };
http_request.open("POST", "https://sso.moxio.com");
http_request.withCredentials = true;
http_request.setRequestHeader("Content-Type", "application/json");
http_request.send({ 'request': "authentication token" });


We're sending a POST request that contains JSON and we'll include our cookies. It produces a request with these headers:

Origin: https://foo.app.moxio.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

Preflight request

Before the AJAX request is made the browser will perform a preflight request. This is an OPTIONS request that the browser will use to check the policy. So when you're implementing the CORS policy on the server remember to also send the policy for OPTIONS requests.

Receiving the request in PHP

Here is an example implementation for the server in PHP:

if (isset($_SERVER["HTTP_ORIGIN"]) === true) {
	$origin = $_SERVER["HTTP_ORIGIN"];
	$allowed_origins = array(
		"http://public.app.moxio.com",
		"https://foo.app.moxio.com",
		"https://lorem.app.moxio.com"
	);
	if (in_array($origin, $allowed_origins, true) === true) {
		header('Access-Control-Allow-Origin: ' . $origin);
		header('Access-Control-Allow-Credentials: true');
		header('Access-Control-Allow-Methods: POST');
		header('Access-Control-Allow-Headers: Content-Type');
	}
	if ($_SERVER["REQUEST_METHOD"] === "OPTIONS") {
		exit; // OPTIONS request wants only the policy, we can stop here
	}
}

There are several important things that this example shows:

  1. http and https are different origins.
  2. We don't allow all origins. You can set Allow-Origin to '*' to allow all origins. This can be useful if you have a public facing API.
  3. We don't expose the list of allowed origins. You can set Allow-Origin to a comma-seperated list of domains but this is more information than the request needs. Since CORS is primarily a security feature it makes sense to set it as restrictive as possible.
  4. For the preflight request we only need to return the CORS policy, there is no need to process the request fully.
  5. For the ACA-headers you can implement a dynamic whitelist (like we do for the domains). We don't do that because we always send the same headers.

Alternatives to CORS

Browser support for CORS is good these days. In the past you might have had to use ActiveXObject or XDomainRequest in Internet Explorer - which offered limited functionality.

JSONP can be used to circumvent CORS restrictions but it comes with it's own share of limitations.

Another way around CORS is by proxying the request through the server on your domain. This is not always a bad solution, particularly if you want to take advantage of caching or if you want to tailor the API.

You can also disable CORS policy checking in some browsers, this might be useful in development.

Final thoughts

We hope to have demonstrated a practical use case for cross domain requests and how to implement them.

We haven't talked about security considerations of our single sign-on model. That's a topic for another time. The short of it is: use https for all requests, protect your passwords and generate strong tokens.

If you want to learn more about CORS we recommend looking at the Mozilla Developer Network page on CORS. MDN is a great resource for referencing native JavaScript APIs.

AJAX learning HTTP Single sign-on CORS PHP headers EN

Deel deze blog