API security and CORS: a NodeJS implementation

NodeJS logo

What is CORS

By default, browsers will block certain requests if both the client and the server are not in the same origin. Cross-origin resource sharing (CORS) is a specification designed to allow restricted resources from a remote server in a given origin, to be requested by a client associated to a different origin. An origin, as defined by the RFC6454, implies “identical schemes, hosts and ports”.

Usually the request from the browser will be accompanied by its corresponding HTTP headers, including the request’s origin. Example of the HTTP headers on the request:

Host: localhost:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://localhost:3001/
Origin: http://localhost:3001
Connection: keep-alive
Cache-Control: max-age=0
If-None-Match: W/”10-iv0euXUvX8F10Ha2yy45d6DFMcI”

How does CORS work?

When CORS is not enabled, the response will not contain the Access-Control-Allow-Origin header and the browser will likely block it, as illustrated by the diagram below.

You will notice that although both the API and the client are in the same domain, the different HTTP ports result in both having different origins.

A Node JS CORS implementation

For this example, we will use a React JS application fetching data from a Node JS API. The principles explained are, however, independent on the technologies used and can be assumed for other scenarios involving cross-origin HTTP communication as well.

index.js (Node JS API)

// API
const express = require('express');
const cors = require('cors')

const app = express();
const port = 3000;

app.get('/cars', (request, response) => {
  return response.json([
    'Mercedes',
    'BMW',
    'Toyota',
    'Audi'
  ]);
});

app.listen(port, () => {
  console.log('   >  backend started...')
});

app.js (React JS front-end)

// front-end
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {

  const [cars, setCars] = useState([]);
  
  async function fetchData() {
    const response = await fetch("http://localhost:3000/cars");
    response.json()
      .then(res => setCars(res))
      .catch(err => console.log(err));
  }

  useEffect(() => {
    fetchData();
  },[]);

  return (
    <div className="App">
      <ul>
        {cars.map(car => <li key={car}>{car}</li>)}
      </ul>
    </div>
  );
}

export default App;

This API has currently no CORS implementation and therefore, when the client tries to fetch the remote data, the browser will deny and throw the following error in the console:


“Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:3000/cars. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)”

How can I fix this?

In order for the browser to allow the request, it is necessary for the API to send along the response, the appropriate HTTP header, as mentioned above. For that effect, we will use the CORS Node JS package.

Let’s begin by installing the package:

npm install cors

Then we can simply require it and add the necessary middleware, like so:

index.js with CORS enabled (Node JS API)

const express = require('express');
// Require the CORS package
const cors = require('cors');

const app = express();
const port = 3000;

// Implement CORS
app.use(cors());

app.get('/cars', (request, response) => {
  return response.json([
    'Mercedes',
    'BMW',
    'Toyota',
    'Audi'
  ]);
});

app.listen(port, () => {
  console.log('   >  backend started...')
});

It is of course possible to set different options. According to the documentation, the default options are:

{
“origin”: “*”,
“methods”: “GET,HEAD,PUT,PATCH,POST,DELETE”,
“preflightContinue”: false,
“optionsSuccessStatus”: 204
}

And you can easily modify them by sending an “options” object to cors().

var corsOptions = {
  origin: 'http://localhost:3333',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

app.use(cors(corsOptions));

After that, the client will be able to successfully accept the data and you will see in the browser console the correct HTTP header.

What if I don’t control the API?

If you do not control the API, the easiest method to bypass the lack of CORS headers might be by the implementation of a proxy that will intercept the headers from the API and will add the missing Access-Control-Allow-Origin header to the transformed response for the client’s consumption.

There is a famous package called CORS-Anywhere deployed in https://cors-anywhere.herokuapp.com, that implements such a proxy. To use it, you can simply append the request URI to the end of its URL and the proxy will transform the headers for you.

app.js (React JS front-end with CORS-anywhere)

// front-end
...
  async function fetchData() {
    const response = await fetch("https://cors-anywhere.herokuapp.com/https://example.com/cars");
    response.json()
      .then(res => setCars(res))
      .catch(err => console.log(err));
  }
...

You can find alternative packages for the proxy, like cors-escape or others. Otherwise, if you are brave enough, you can always have some fun coding your own CORS proxy.

Leave a Reply

Your email address will not be published.

Loading Facebook Comments ...
Loading Disqus Comments ...