Frontend Fundamental - AJAX, JSONP and CORS

Sometimes front-end noob like me will run into Same-Origin-Policy when dealing with ajax things. This article is a note for myself to get clear of what is it and how to deal with it when encounter.

Same-Origin-Policy

Same-Origin-Policy is a concept of preventing malicious script from getting sensitive data through [CCRF](Cross-site request forgery) and XSS. Under this policy, browser can access resources only if current document and target server are under the same domain.

The question is, why do we need to access data from other domain? The reason is that in web development, sometimes we store resources like html, css and js on different server/domain in order to reduce the burden of server. Another reason is that today’s web is RESTful, which means client sends request to server to get operation on resources.

AJAX

One way to access cross domain resource without violating SOP is using current server that sends resources to us as a proxy.

By doing this, fetching cross domain resources is our server’s job now since SOP only works on browser, not server.

client’s request <—> our server as proxy <—> another server

JSONP

SOP restricts where we can request resources we want, however, there are still exceptions.

These exceptions are script, img, link tags.

According to the draft of origin (or this question from stackoverflow if you are lazy like me), it says that because not wanting to make web development a painful thing, they decided not to treat every page as an independent origin, and thus they made some exceptions.

The spirit of this approach is that you still need the server holding resources you want implements a way to provide this service. We will see how to do this with following code. Let’s code!

title:"server 1"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var http = require("http");
var fs = require("fs");

var html = null;

fs.readFile("jsonp.html", function(err, file){
if(err){
throw err;
}
html = file.toString();
});

http.createServer(function(req, res){
res.writeHeader(200, {
"content-type": "text/html"
});
res.end(html);
}).listen(3000);

console.log("domain 1 is running...");
title:"server 2"
1
2
3
4
5
6
7
8
9
10
var http = require("http");

http.createServer(function(req, res){
res.writeHeader(200, {
"content-type": "text/plain"
});
res.end("Hello JSONP!");
}).listen(3001);

console.log("domain 2 is running...");
title:"jsonp.html"
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
<!DOCTYPE html>
<html>
<head>
<title>jsonp test</title>
</head>
<body>
<div id="text"></div>
<button id="button">ajax!</button>
<script>
function contentLoaded(){
var text = document.getElementById("text");
var button = document.getElementById("button");
button.addEventListener("click", function(e){
var ajax = new XMLHttpRequest();
ajax.addEventListener("readystatechange", function(){
if(ajax.readyState === 4){
if(ajax.status === 200){
text.appendChild(document.createTextNode(ajax.responseText));
}
}
});
ajax.open("GET", "http://localhost:3001");
ajax.send();
});
}

window.addEventListener("DOMContentLoaded", contentLoaded);
</script>
</body>
</html>
title:"Result in Chrome console"
1
XMLHttpRequest cannot load http://localhost:3001/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.

Well, of course it doesn’t work since it violates SOP. Let’s correct this.

title:"server 2"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var http = require("http");
var url = require("url");
var querystring = require("querystring");

http.createServer(function(req, res){
var queryTemp = url.parse(req.url).query;
var query = querystring.parse(queryTemp);

res.writeHeader(200, {
"content-type": "text/plain"
});
res.end(query.callback + "('Hello JSONP!')");
}).listen(3001);

console.log("domain 2 is running...");
title:"jsonp.html"
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
<!DOCTYPE html>
<html>
<head>
<title>jsonp test</title>
<script id="jsonp"></script>
</head>
<body>
<div id="text"></div>
<button id="button">jsonp!</button>
<script>
var text = document.getElementById("text");
var button = document.getElementById("button");
var jsonp = document.getElementById("jsonp");

function contentLoaded(){
button.addEventListener("click", function(e){
jsonp.src= "http://localhost:3001/?callback=changeText";
});
}

function changeText(string){
text.appendChild(document.createTextNode(string));
}

window.addEventListener("DOMContentLoaded", contentLoaded);
</script>
</body>
</html>

When you click jsonp! button text div should be appened with Hello JSONP! text.

CORS

Cross-Origin-Resource-Sharing, CORS, is a standard mechanism that let you access cross-domain resources with standard ajax request. You don’t need those sorcery ways to do the magic anymore.

By simply enabling the following header in server 2:

Access-Control-Allow-Origin: http://localhost:3000

Visit here if you want more headers for customization.

title:"server 2"
1
2
3
4
5
6
7
8
9
10
11
var http = require("http");

http.createServer(function(req, res){
res.writeHeader(200, {
"content-type": "text/plain",
"access-control-allow-origin": "http://localhost:3000"
});
res.end("Hello CORS!");
}).listen(3001);

console.log("domain 2 is running...");
title:"index"
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
<!DOCTYPE html>
<html>
<head>
<title>CORS test</title>
</head>
<body>
<div id="text"></div>
<button id="button">CORS rocks!</button>
<script>
function contentLoaded(){
var text = document.getElementById("text");
var button = document.getElementById("button");
button.addEventListener("click", function(e){
var ajax = new XMLHttpRequest();
ajax.addEventListener("readystatechange", function(){
if(ajax.readyState === 4){
if(ajax.status === 200){
text.appendChild(document.createTextNode(ajax.responseText));
}
}
});
ajax.open("GET", "http://localhost:3001");
ajax.send();
});
}
window.addEventListener("DOMContentLoaded", contentLoaded);
</script>
</body>
</html>

We are now be able to acess cross-domain resources in standard ajax way without any errors, weeeeeeeeee!

References

Frontend Fundamental - HTTPS

What is HTTPS?

HTTPS stands for HTTP Secure(or HTTP over TLS, SSL). The purpose is to establish a encrypted connection which provides more protection as opposed to HTTP that only creates transparent connections that are under the risk of being eavesdropping or intercepted by man-in-the-middle.

HTTPS differs from HTTP at some points -

  • HTTPS uses port 443 as default, while HTTP uses port 80.
  • HTTPS still uses HTTP as its essential communication method. You can take SSL/TLS as an “upgraded connection” as opposed to “normal connection” which HTTP uses with.

What is TLS/SSL?

SSL stands for Secure Sockets Layer, and TLS stands for Transport Layer Security. They are both cryptographic protocol making network communication more secure.

SSL is the predecessor to TLS. SSL was started by Netscape, and was upgraded to TLS in 1999 because Netscape was no more and thus no further maintenance from then on.

Today we still refer to TLS as SSL, because historically they are the same thing, only name changed.

How does it work?

Generally, TLS/SSL works with private key ,public key and certificate.

  • Client and server have its own private keys. Public key is the same on both client and server.
  • Certificate carries public key and other necessary informations.
  • Certificate can exist on both client and server.
  • Who sends certificate depends on method.

In order to use HTTPS, website owner should do the following:

  1. Generate Certificate Signing Request (CSR). By doing this, your computer will generate a private key.
  2. Go to a trusted third party Certificate Authority (CA), and validate with your CSR.
  3. When validation ends, CA will give you a new public key encrypted with CA’s private key.
  4. Install this CSR on your server machine.

Client and server establish connection by using a handshaking procedure:

tl;dr Version

In case you are impatient like me…

  1. Server sends server’s public key (certificate) to client.
  2. Client checks if it’s expired or not listed on the browser’s certificates authority list.
  3. If this certificate is trusted, client will send back its client’s public key to server.
  4. Server creates an unique hash and encrypts it with client’s public key and server’s private key and send it to client.
  5. Client decrypts the hash with client’s private key and server’s public key, and verifies it.
  6. Server and client start to communicate in cryptographic communication.

Basic TLS Handshaking

  1. First, client and server must agree on both using TLS/SSL protocol (HTTPS).
  2. Client sends a Client Hello message. In this step, client also sends informations like the highest TLS version it supports, cipher suites and suggest compression methods.
  3. Server responds with a Server Hello message, containing the chosen TLS version, a random number, the choices of cipher suites and compression method which client offered.
  4. Server sends a Certificate to client.
  5. Server sends a Server Key Exchange message to client.
  6. Server sends a Server Hello Done message to client.
  7. Client responds with Client Key Exchange, which may contains with PreMasterSecret, public key or nothing(depends on cipher suites choice).
  8. The client and server will now use random number and PreMasterSecret to generate a MasterSecret, which will be used by the following key data.
  9. Client sends a Change Cipher Spec message back to server. That means, “Everything I tell you now on will be authenticated.”
  10. Client sends a encrpted Finished message. Server will attempt to decrpt the Finished message. If it fails, the connection will torn down.
  11. Server sends a Change Cipher Spec message to client, telling “Everything I tell you now on will be authenticated.”
  12. Server will do the same thing like what client does in step 9.

Client-Authenticated TLS Handshaking

The procedure is mostly identical to basic TLS handshaking, but with some changes from step 4:

  1. Server sends its own Certificate to client.
  2. Server sends Server Key Exchange.
  3. Server requests client’s Certificate.
  4. Server sends Server Hello Done.
  5. Client responds with its Certificate.
  6. Client sends Client Key Exchange.
  7. Client sends a Certificate Verify message, which is a signature over the previous message using client’s private key. Later will be verified by server with client’s public key.
  8. After this step is identical to step 9 of basic TLS handshaking.

Cipher Suites

You may wonder what is cipher suites, right? Here’s what it defines:

  • Key exchange algorithm - how client and server authenticate during connection.
    • RSA, Diffie-Hellman, ECDH, SRP, PSK.
  • Encryption algorithm - the method to encrypt message stream.
    • RC4, Triple DES, AES, IDEA, DES…
  • Message authentication code(MAC, some refer to hash) - the way to create a cryptographic hash.
    • For TLS: HMAC-MD5, HMAC-SHA.
    • For SSL: SHA, MD5, MD4, MD2….

Pros and Cons

Pros

  • Security, security and security! No one will risk exposing their password, right?

Cons

  • Complex handshaking means more bandwidth overhead.
  • Since it costs more bandwidth, you may not want to use HTTPS on every page of your site, and by separating our protocol into two may lead more complexity to our system architecture.

Acquiring Certificates

Acquiring a valid certificate means you need to purchase it issued by 3rd party Certificate Authority.

There are tons of CA on the Web, and since this article is not about getting your best deal SSL certificate, I will use openssl to generate a self-signed certificate for the following demonstration, and remember self-signed certificate is for testing/development purpose. Don’t ever use it in production, or you will scare customers away.

Generate a Private Key

openssl req -nodes -new -x509 -keyout server.key -out server.cert

Here I use req command to generate a certificate and a private key under current directory.

By doing this, the following form will prompt out. Type whatever it requests, and remember to leave challenge password empty.

1
2
3
4
5
Country Name (2 letter code) [AU]:TW
State or Province Name (full name) [Some-State]:Taiwan
Locality Name (eg, city) []:Taipei
Organization Name (eg, company) [Internet Widgits Pty Ltd]:meow
...

HTTPS In Practice

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var https = require("https");
var fs = require("fs");

var options = {
key: fs.readFileSync("./server.key"),
cert: fs.readFileSync("./server.cert")
};

https.createServer(options, function(req, res){
res.writeHeader(200, {
"content-type": "text/plain",
});
res.end("Hello World");
}).listen(3000);

console.log("Server is running...");

Let’s proceed with Chrome…

https-big

Of course, our certificate is not issued by authorized 3rd party CA, and thus not trusted.

https-small

At least, it works with our Node.js, yay~

References