Connect Handler Example for Simple CORS Requests

For reference, I’m recording an example of handling Simple CORS Requests with some handy connect/express implementation details.

Remember when using webapp.use() with a path as the first argument:

  • All sub-paths of that path are also handled
  • The specified path is striped from the beginning req.url inside request handlers

Notes about the req parameter to request handlers:

  • req objects are instances of node’s http.IncomingMessage
  • Your framework may build on top of the IncomingMessage interface. For example, express adds the param method
// hosts whitelist
var coorsAccessOrigins = {
  'http://localhost:3000': true,
  'http://example.com': true,
  'http://charlesholbrow.com': true,
  'http://www.charlesholbrow.com': true
};

// allow simple CORS GET requests to the /dds directory
webApp.use('/dds', function(req, res, next){
  // Don't add access control headers if host is not in whitelist
  if (!coorsAccessOrigins[req.headers.origin]) return next();

  // The origin header implies this is a CORS request.
  // The user-agent (the browser) is unlikely to add the origin
  // header if it doesn't support CORS
  if (!req.headers.origin) return next();

  res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  res.setHeader('Access-Control-Allow-Methods', 'GET');
  return next();
});

Notice the difference between the origin request header and the host request header.

  • The host request-header specifies the Internet host and port number of the resource being requested
  • The origin request-header indicates that this is a CORS request
// example req.headers object
{
  "host": "localhost:3000",
  "connection": "keep-alive",
  "cache-control": "no-cache",
  "pragma": "no-cache",
  "origin": "http://charles.meteor.com",
  "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
  "accept": "*/*",
  "dnt": "1",
  "referer": "http://charles.meteor.com/",
  "accept-encoding": "gzip,deflate,sdch",
  "accept-language": "en-US,en;q=0.8",
  "x-forwarded-for": "127.0.0.1",
  "x-forwarded-port": "3000",
  "x-forwarded-proto": "http"
}

Cross-Origin Resource Sharing and JSONP: A Simple Explanation

If you are browsing farmville.com and a script downloaded makes an ajax request to facebook.com, this is known as cross-origin resource sharing, or CORS.

When a modern browser encounters a CORS request, it intervenes for security reasons. Browsers distinguish Simple Requests from Non-Simple Requests using a different intervention strategy for each.

Simple Requests

An HTTP request is considered simple if all 3 criteria are met:

  1. The request method is GET, HEAD or POST
  2. the request uses only headers in the following list:
  • Accept
  • Accept-Language
  • Content-Type
  • Content-Language
  1. The value field of the Content-Type header matches one of the following:
  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

For simple requests, the browser adds the Origin header behind the scenes. The request received by our server might look like this:

GET /some/resource
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Origin: http://foo.example

The server observes the Origin header, and serves back a response. The browser inspects the HTTP Response and looks for the Access-Control-Allow-Origin header.

Access-Control-Allow-Origin: http://bar.example.com

In the example above, the server is indicating that only requests originating from http://bar.example.com are permitted to access the resource. The browser will discard the data in the http response, and throw a JavaScript error indicating that the AJAX request was unsuccessful. Note that wild cards can also be used for the value of Access-Control-Allow-Origin. In the next example, the server is indicating that requests from any origin may be permitted to access the response.

Access-Control-Allow-Origin: *

The Access Control headers hidden from the JavaScript API - they are removed be the browser before exposing the HTTP Response to your JavaScript code.

Non-Simple Requests

For non simple HTTP Requests, the browser employs a different security strategy. Instead of sending an ajax request directly to the other server, it first sends an OPTIONS http request. The headers in the response to the OPTIONS request indicate to the browser if the server is willing to listen to requests originating from other servers.

In our example, facebook.com needs to be configured to respond to OPTIONS requests with a specific header format. These headers indicate to the browser which domains are allowed to make resource requests.

CORS is the modern way to make cross-origin requests – It’s not supported by IE 7 and below. JSONP is another option for making cross origin requests.

JSONP exploits the fact that <script> elements are allowed to make cross-origin GET requests to retrieve script files. A server can return an html document containing a script tag with a cross origin src='www.example.com' attribute. The browser will not interfere with this request.

<script>
var myFunc = function(data){
  console.log(data);
};

// append a script tag to the document. This will trigger a cross-origin GET request.
document.write("<script src='http://facebook.com/example/api/callback=myFunc'>")
</script>

The script returned by Facebook’s executes a function that we (hopefully) defined prior to making the request.

// example script returned and run by dynamically adding a script tag
myFunc({
  name: 'Charles',
  id: '123456'
});

DNS, CNAME, A Record, and HTTP Redirect Explained

You have probably noticed that different websites handle the www subdomain in different ways. For Example:

It’s worth understanding DNS, A Records, CNAME, the HTTP Host Request Header, and HTTP Redirects so you can make your domain function the way that you want.

DNS - Domain Name System

When you buy a domain, your registrar allows you to specify Authoritative Name Servers. For example you might register ns1.domaincontrol.com as the authoritative resource on the internet for identifying the IP address of your domain.

A Records and CNAME

Once your Authoritative Name Server is registered, you configure that name server, specifying the IP addresses that your domain and (optionally) subdomains point to.

An A Record (Address Record) points your domain or subdomain to an IP Address. An A Record will always point to an IP Address.

A CNAME (Canonical Name aka Alias) points your domain or subdomain to to the the IP Address of a different domain. CNAME records always point to domain names.

The **@** symbols is used in DNS records to indicate your root domain name. Consider the hypothetical DNS table below for the domain writersreply.com

Host Name Address Type
@ 18.85.28.11 A
www writersreply.com CNAME

The example above doesn’t actually redirect users - it just means that when you navigate your browser to www.example.com the HTTP Request goes to the same IP address as if you navigate to example.com. In either case the address bar in your browser will be unchanged when the site loads.

HTTP Redirects

You can’t redirect users with DNS Configuration. However, some DNS providers offer a redirection service. If your Domain registrar does not offer redirection service, you have to configure your web server to redirect http traffic manually.

How does this work? Here’s an example of browser behavior when you navigate to google.com:

  1. Use the DNS IP address provided by your Router or ISP to determine the Authoritative Name Server for google.com
  • Answer: ns1.google.com
  1. Request the ip address of google.com from ns1.google.com
  • Answer: 74.125.21.100
  1. Send an HTTP Request to 74.125.21.100 looking something like this:
GET / HTTP/1.1
Host: google.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
DNT: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36
X-Chrome-UMA-Enabled: 1

Notice that the HTTP request is sent to an IP address, but it includes the Host Header google.com. Google’s servers see the host name in the request, and send back an HTTP 301 Redirect looking akin to this:

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Server: gws
Content-Length: 219
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Alternate-Protocol: 80:quic
Age: 25150
Date: Sun, 01 Jun 2014 13:09:35 GMT
Expires: Tue, 01 Jul 2014 13:09:35 GMT
Cache-Control: public, max-age=2592000
Connection: keep-alive

Important points

  • The only thing that DNS acutally does is tie Domain names to IP Addresses
  • HTTP Requests are sent to IP addresses, but include the domain name in the Host Header
  • HTTP servers may serve different content depending on the contents of the Host Header

The exact way you configure your server to redirect http requests depends on the server you are using.

  • Apache servers can redirect via the .htaccess file (Stackoverflow)
  • Nginx servers can redirect via server block (Stackoverflow)
  • Node.js servers inspect the HTTP Host header, and headers modify the response object
// Inside node.js request handler
response.writeHead(302, {
  'Location': 'your/path/here',
  // optional other headers...
});
response.end();

I hope

I hope I live to see the day when we look back at this time period and say:

They used money as a measure of success at all scales - how primitive!

What Should a Node Module Export?

A node.js module can export:

  • A function that we call when we import a module
  • An object with properties
  • Both! (a function with assigned properties)

module is the global scope variable in a node.js file. It behaves similarly to window in the browser.

// Assignment can hide the "exports" name
// in a node.js file:
var exports = 'Caution!' // creates a new exports object that will NOT be exported
exports = 'Same problem!' // same problem as above

The controller pattern (exemplified below) exports a function

var app;
var templates;
var emailController;

// Code outside of the exports function will be run once the 
// first time we require this module is required
var mongoose = require('mongoose');
var User = mongoose.model('User');
var controller = {};

// if the exports function on a module requires the app,
// pass express app in when we require this controller
module.exports = function(_app) {
  app = _app;

  // if we know that 'templates' was set on the app before
  // requring this controller, then we can app.get 'templates'
  // This will re-set the templates variable every time that 
  // this module-function is called.
  templates = app.get('templates'); 

  // We can also require modules directly on the exports
  // function. Asume that the Email module uses the same
  // module.exports = function(_app) pattern as this 
  // module.
  emailController = require(__dirname + 'controllers/Email')(app)

  return controller;
}

controller.getAPI = function(req, res, next) {...};

What is an express.js app?

An express app is built on top of the npm connect module. The express docs feel incomplete, because they do not cover behavior inherited from connect.

Express apps begin like this:

var express = require('express');
var app = express();

The source of the express function looks like this (github):

function createApplication() {
  var app = connect();
  utils.merge(app, proto);
  app.request = { __proto__: req, app: app };
  app.response = { __proto__: res, app: app };
  app.init();
  return app;
}

Connect Prototype

The npm connect module is the foundation of our express app. The source of the connect() function above looks like this (github):

function createServer() {
  function app(req, res, next){ app.handle(req, res, next); }
  utils.merge(app, proto);
  utils.merge(app, EventEmitter.prototype);
  app.route = '/';
  app.stack = [];
  for (var i = 0; i < arguments.length; ++i) {
    app.use(arguments[i]);
  }
  return app;
};

The code above uses utils.merge to give app all the functionality of the Connect http server prototype. This includes the .use, .handle, and .listen methods.

The app also merges EventEmitter from the node.js events library. EventEmitter is where app gets app.on and app.emit from.

app.listen returns a server

app is itself a request handler that is passed to node’s http.createServer when you call app.listen

var port = 3000;
var server = app.listen(port);

app.listen is defined in the connect prototype. It looks like this:

// require 'http'
app.listen = function(){
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

http.createServer returns a net.Server instance from node’s net module.

Express App Prototype

After the connect initializaition has finished, we merge the express app prototype onto our new connect app.

utils.merge(app, proto);

This line adds all the functionality from the express application prototype. This is where app.use, app.routes, app.get, etc are defined. This behavior is documented very clearly in the express docs.