When accessing API calls from another domain or
port number, we have to be mindful of the possible CORS policy
restrictions. CORS stands for Cross-Origin Resource Sharing. This is used
to prevent access from one domain to another without having the policy to
enable that access. A common use case is one an application hosted on one
domain tries to access the API hosted on a remote domain. When the calling
domain is not whitelisted for access, an error is raised and the call is
denied.
Access Error
To illustrate this problem, we can look at an
app hosted on our localhost but with a different port number. We should notice
that port number makes the policy applicable even when the domain is the same.
Access to XMLHttpRequest at
'http://localhost:5000/api/data' from origin 'http://localhost:8080' has
been blocked by CORS policy: Response to preflight request doesn't pass access
control check: No 'Access-Control-Allow-Origin' header is present on the
requested resource.
|
When making a CORS request to a different
domain, there would be two requests made to the server, the preflight and
actual requests. For each of these requests, the server must respond with the
Access-Control-Allow-Origin header set with the name of the domain of origin
(calling app) or a wildcard ‘*’ to allow all domains. Wild card are a bit too
open, so this is typically not used for secured apps.
Preflight Request
A preflight or OPTIONS (HTTP verb) request is
created by the browser before the actual request (PUT, POST) is sent for a
resource in different domains. The goal is to have the browser and server
validate that the other domain has access to that particular resource. This is
done by setting the Access-Control-Allow-Origin header with the client host
domain.
Actual Request
Once the preflight request has a response with
the corresponding headers and HTTP 200 status, the browser sends the actual
request. For this request, the server also checks the CORS policies and adds
the Access-Control-Allow-Origin header with the client host domain.
NodeJS Middleware
Now that we understand about this security
concern, we can take a look at how we can build a middleware with NodeJS to
help us manage this integration.
Let’s start by defining a common NodeJS Express
application. When we build the bootstrap code for the server, we can include a
middleware module (auth-cors) which is our implementation to handle the CORS
concerns. This is done by loading the module using the require directive and
loading the module script relative to where the loading script is found. Let’s
review the code to see how we can do that.
const express = require("express");
// middleware for
cors
const cors = require("./middleware/auth-cors");
const PORT = process.env.port || config.port;
const app = express();
// initialize
modules
app.use(bodyParser.json());
app.use(
bodyParser.urlencoded({
extended:
true
})
);
// add the cors
middleware to the http pipeline
app.use(cors);
// start the server listener
app.listen(PORT, () => console.log(`Listening on ${PORT}`));
|
After we load the CORS module, we can associate
it with the application by calling app.use(cors). This is a way to tell the
server application to pass the context references to each module in the
HTTP pipeline.
Now that we understand how to include the middleware
module, we can take a look at the actual implementation below. We start by
exporting the module definition and implementing the RequestHandler interface
contract which enables the module to receive the HTTP pipeline context with the
request, response, and next references. The next context enables the code to
continue the pipeline request execution to complete. This is the context that
all middleware modules should return to chain the other middleware modules in
the pipeline.
module.exports = (req, res, next) => {
if (req.headers.origin) {
// add the remote domain or only the
environment
//
listed on the configuration
res.setHeader(
"Access-Control-Allow-Origin",
process.env.domain || req.headers.origin
);
}
// Request methods you wish to allow
//
remove the methods you wish to block
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS, PUT, PATCH,
DELETE"
);
// Request headers you wish to allow
res.setHeader(
"Access-Control-Allow-Headers",
"Origin,Authorization,X-Requested-With,content-type,Accept"
);
// Set to true if you need the website to
include cookies in the requests sent
// to the API (e.g. in case you use
sessions)
res.setHeader("Access-Control-Allow-Credentials", true);
if ("OPTIONS" === req.method) {
return res.sendStatus(200);
}
return next(); // Pass to next layer of middleware
};
|
What is the module really doing?
The module basically intercepts the request from
the HTTP pipeline to add the headers that can enable a client application to
send a request from another domain.
The Access-Control-Allow-Origin header is added to the response header to include the remote
domain. This is the area where we can whitelist some domains and not allow
others. In this example, we are just adding the remote domain which should not
be the normal case. The same approach is taken to Allow Methods and Headers
that our application supports. For example, to block the Delete method, we do
not add that value in the header.
The other interesting header to include or
exclude based on your application requirements is the Access-Control-Allow-Credentials header.
This is used for cases when you want the client application to send back
credential information in the header. This is useful for session management
state and authorization token information.
Summary
It is important to understand that we run an
application and APIs on different domains, we have some security concerns that
we need to plan for. This can enable our apps to safely use remote APIs and
prevent malicious attacks to our servers. With NodeJS, building middleware
components to handle this concern is fairly simple. Most of the understanding
is around how the HTTP protocol works to enable us to handle this
integration.
Please let me know your feedback, and I
will add clarification to the article.
Thanks for reading