Integrate a Node.js SPA with a PHP Web App

Quick definitions:
SPA = Single Page Application
Authentication = Who are you?
Authorization = What you are allowed to do?
CORS = Cross Origin Resource Sharing

I’ve been working on an interesting problem to seamlessly integrate an existing PHP web app with a newly built SPA running on Node.js.
There are many different approaches to solving this problem and this solution is more of a low level implementation, so a certain familiarity with HTTP headers and cookies is required.
This post also doesn’t describe scalability which I might talk about it in a future post.

This post describes:

  1. How to implement session sharing between PHP and Node.js
  2. How to authenticate and authorize the RESTFul API calls made from the browser
  3. How to secure the internal API calls made between the PHP and the Node server

1. How to implement session sharing between PHP and Node.js

Your user logs into the app, providing some credentials and a cookie or token of some sort is returned, which you use to identify that user.
Your AJAX requests to the API server will carry that same logged-in token (PHPSESSID) as before. Then we are checking that token against an internal API on the php server, and restricting the information down to ‘just what the user is allowed to see’.
The important consideration is that RESTful web services require authentication with every request.

The diagrams below shows the interactions between the servers:

2. How to authenticate and authorize the RESTFul API calls made from the browser

Same-origin-policy and CORS

Since the PHP and Node API server are running on different IP addresses the HTTPS requests made using the XMLHttpRequest object are subject to the same-origin policy. This means that HTTP requests could only be made to the domain the page was loaded from.

The CORS (Cross Origin Resource Sharing) mechanism provides a way for web servers running on different IPs or domains to support cross-site access.

Client side code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
options.crossDomain ={
crossDomain: true
};
options.xhrFields = {
withCredentials: true
};
});

function getSessionId(){
var jsId = document.cookie.match(/PHPSESSID=[^;]+/);
if(jsId) {
if (jsId instanceof Array)
jsId = jsId[0].substring(10);
else
jsId = jsId.substring(10);
}
return jsId;
}

var id = 'myRequestedDataId';
// make ajax call
var pathAndQuery = "/api/myapi";
$.ajax({
url: 'https://' + API_HOST + pathAndQuery,
type: 'GET', // or POST
beforeSend: function(request) {
request.setRequestHeader("id", id);
request.setRequestHeader("PHPSESSID", getSessionId());
},
success: function(data) {
var json = JSON.parse(data);
if(json.status === "success") {
// do stuff
}
},
error: function(e) {
console.log(e);
// navigate to the login
window.location.href = '/index.php';
}
});

Node.js code for implementing cors headers:

1
2
3
4
5
6
7
8
9
10
11
12
13
var https = require('cors');
var corsOptions = {
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: true,
origin: 'https://your.phpserver.com' || process.env.WEBORIGIN,
credentials: true,
allowedHeaders: 'phpsessid,id'
};

var app = express();

app.use(cors(corsOptions)); // support cors requests
app.options('/api', cors(corsOptions)); // enable pre-flight requests

PHP code for verifying the session cookie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$sid = $_REQUEST["PHPSESSID"];
$id = $_REQUEST["id"];
// Set the session to the supplied session id.
session_id($sid);
session_start();
$cid = $_SESSION[APP]['client_id'];
if (empty($cid) {
// invalid session id
$error = "Failed to match session.";
}
// continue and validate id
// ...

// Output the json response:
if (empty($error)) {
$json = "{\"status\": \"success\"}";
} else {
$json = "{\"status\": \"error\"}";
}
echo $json;
?

3. How to secure the internal API calls made between the PHP and the Node server

Depending on how strong the security needs to be, there are several approaches securing the internal APIs, some of them are:
-Restricting the IP/PORT to only allow access from the internal servers.
-Using TSL in combination with basic authentication. If the security needs are not as high this is an easy way of implementing security.
-Token based security e.g. oAuth.
-Using Client-authenticated TSL Handshakes, below is a good article:
https://engineering.circle.com/https-authorized-certs-with-node-js-315e548354a2#.sakue1rg6