Welcome to part 3. Follow along this series to develop a feature-packed 3-tier Authentication System outlined in successive parts.
Summary: In this article we will learn to configure an express backend application to understand CORS Protocol.
Note 🔔:
If you would like to jump ahead to the final Authentication System put together, the complete Git Repo can be found on Github✩.
Let's start here with an example case.
You have built a stunning user interface 😎. Now you are ready to pull data from external API and inject in the UI for display. You have scripts that make HTTP requests to API. But something is standing in the way 🛑. The console blazes fiery red and ranting🗣️:
Tellingly, you have run into CORS error.
Table of ContentsWhy CORS Error?
Using the example above, this error occurs because UI is loaded on a different port, i.e http://localhost:3000 and the scripts in it are requesting to a different port i.e http://localhost:8000. So scripts are making cross-origin requests. Well, Same-origin policy is violated! And therefore the browser blocks such a request 👊.
Note: Two URLs have the same origin if the protocol, port (if specified), and host are the same for both.
It is a security motive that browsers restrict cross-origin HTTP requests initiated from scripts. "XMLHttpRequest" and "Fetch" are browser APIs that adhere to same-origin policy. This means that a web application using these APIs can only request resources from the same origin the application was loaded from. In layman's language, scripts in a web page can "peacefully" request to the URL shown in the address bar without being hurled with confrontations by the browser. This way, the nightmarish CORS error is well tucked out of sight.
But with special configurations, cross-origin requests can succeed. However, these configurations need to be done on the backend rather than the frontend. These configurations are what we will be looking at in this article.
So,
What is CORS?
CORS is an abbreviation for Cross-Origin Resource Sharing.
It is a standard that allows a server to moderate same-origin policy in browsers, so cross-site requests made to it can be allowed. And this involves sending appropriate CORS headers that will be verified in the browser.
Ultimately this means that if you run into a CORS error, the fix lies in backend configuration.
Getting started
This is how we organize the nodejs project.
Start by creating a server
directory and change into it:
d="server" && mkdir $d && cd $d
Inside server
directory, run npm init -y
to create a package.json
file. Add/edit the package.json
file to include the following script:
// ...
"scripts": {
"start": "node src/index.js",
}
// ...
Run npm install express --save
to install express web framework.
Then create src
directory(mkdir src
) inside this server
directory. Below is the overall directory structure.
Directory structure
📂server/
├── 📄package.json
└── 📂src/
├── 📄index.js
+ └── 📂config/
+ └── 📂cors/
+ └── 📄cors.js
This is the structure with focus on the src
directory. The src
directory will contain files with code we write for our project. We will add code in these files as we progress through the article. You can always double check to create these files in their correct locations.
Enable CORS
We are going to configure CORS protocol on our express backend. The CORS protocol simply consists of a set of headers that indicates whether a response can be shared with the origin that requested.
The set of headers that can be set on response to a CORS request are listed here.
To reduce the work of setting these headers manually, we will use an npm library called cors. So be sure to install it:
npm install cors --save
Then open the entry point of our application file, i.e index.js
, located at server/src/
(refer). Add the cors
middleware as shown in section 2
:
const express = require("express");
const cors = require("cors");
/*
1. INITIALIZE EXPRESS APPLICATION 🏁
*/
const app = express();
const PORT = process.env.PORT || 8000;
/*
2. APPLICATION MIDDLEWARES AND CUSTOMIZATIONS 🪛
*/
app.use(cors()); // Enable Cross Origin Resource Sharing
/*
3. APPLICATION ROUTES 🛣️
*/
/*
4. APPLICATION ERROR HANDLING 🚔
*/
/*
5. APPLICATION BOOT UP 🖥️
*/
app.listen(PORT, () => console.log("App listening on port " + PORT));
Note: The file index.js
has commented sections that are numbered. Some sections have logic defined in other parts of the series.
Yes, dead simple as that. The CORS headers will be set on HTTP response by the cors()
middleware when the browser sends a preflight request and the actual request.
By default, cors
library sets Access-Control-Allow-Origin
header with a wildcard(*
) value on the actual response.
While this configuration of cors
library works, it will fail for Cross-Orign requests that include credentials. Because a request that includes credentials(e.g Cookie
header), requires additional headers configured and that the headers are set explicitly(not wildcarded). Therefore a header including a wildcard(*
) value will fail.
Credentials are cookies, authorization headers, or TLS client certificates.
Below is an example of a failure message when a request with credentials receives a response with Access-Control-Allow-Origin
header set with a wildcard(*
):
cors
library needs additional configuration to set CORS headers appropriately for requests including credentials.
Enable CORS for credentialed requests
Like said earlier, credentialed requests are requests that include credentials, i.e cookies,authorization headers or TLS client certificates.
The Authentication System we are building in this series will make use of cookies, so it's important to configure our backend to understand CORS protocol for credentialed requests.
Open the file cors.js
which should be available at path server/src/config/cors/
(refer). Create the constituent directories if you do not have.
Paste the following code inside cors.js
file:
const allowlist = ["http://localhost:3000", "http://localhost:8000"];
const corsOptions = {
origin: function (origin, callback) {
if (allowlist.includes(origin)) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
credentials: true,
exposedHeaders: ["WWW-Authenticate"],
};
module.exports = corsOptions;
We are allowing an origin
that is in allowlist
to access our API. Anything else is locked out; represented by passing a first argument to the callback
function: callback(new Error("Not allowed by CORS"));
.
This cors option will explicitly set Access-Control-Allow-Origin
response header to the value of origin that has been cross-checked to exist in the allowlist
.
The next property option is:
credentials: true,
This cors option will set Access-Control-Allow-Credentials
response header to true, which basically gives an okay that our server will accept cookies from a cross-origin request(Client application). The browser requires that this header exists in response to preflight request and before setting a cookie in client device as directed by Set-cookie
header of a response received.
Then we have:
exposedHeaders: ["WWW-Authenticate"],
This cors option will set Access-Control-Expose-Headers
response header to WWW-Authenticate.
This is not mandatory but if you need to access this header(or any other) with scripts running in the browser, then you need to set it.
You may set other options you may need. The options we have set here should be able to understand the CORS protocol for credentialed requests.
What is remaining, is to include these options to the CORS middleware from the entry file of our application. Therefore open index.js
found at server/src/
(refer) and include these options in cors middleware as shown:
const corsOptions = require("./config/cors/cors.js");
// ...
/*
2. APPLICATION MIDDLEWARES AND CUSTOMIZATIONS 🪛
*/
app.use(cors(corsOptions)); // Enable Cross Origin Resource Sharing
// ...
Congrats! This is the way! Now you can seamlessly make cross-site requests to your backend. Whether the request include credentials or not, CORS error should not intrude.
Conclusion
We have looked at how to solve the CORS error you may run into, why it happens and what it really is. CORS error happens because the browser is reminding you to implement security in your application. We have seen how to we can configure CORS in an application that intends to work with requests that include credentials such as cookies.
If you can control the backend, then you can fix CORS error, otherwise the fix is out of your control. You still may opt for hacky workarounds such as proxying requests.
Fixing CORS error generally implies setting response headers.
These specific headers can be set using third-party libraries like cors which is well battle-tested and effective in its job. However, you can still set the headers manually(which creates room for errors 🤕).
Did I leave out something? You can put it in the comments section.
We can connect @twitter and share ideas.
Top comments (0)