loading...

Top 5 CORS Issues You Don't Want To Run Into

heytulsiprasad profile image Tulsi Prasad Originally published at wirescript.now.sh ・7 min read

What is CORS?

CORS stands for Cross Origin Resource Sharing, which uses additional HTTP headers to tell browsers to give a web application running at one origin, access to resources from different origin. For instance, if your frontend is hosted on a different platform than your backend so you'd need to make HTTP requests to get your data from there, which the browser blocks by default (as its hosted on a cross-origin, not same-origin). This is a security measure we take to protect our clients from CSRF attacks. That's where the concept of CORS comes in.

Now I'll walk you through all the CORS errors that kept me up at night this week and how to fix each one of them.

No Access Control Allow Origin header is present

I was completely unknown regarding cors, so I wrote my express app and added a proxy in React's package.json to get access to the backend routes in development. But once I went to production my app stayed in its loading state and my console showed up these errors.

Alt Text

The routes were different as I couldn't grab the screenshot of my own error, but the message was same. It didn't work online although my prod succeeded and everything worked locally.

Its trying to say that our origin is blocked by CORS policy so we can't access the data from backend. It also says, no Access-Control-Allow-Origin header is present in which is a HTTP header which says which origins can have access to our data. We need to add our frontend endpoint on it so it can send all its data to us upon request.

Fix

You can add the mentioned HTTP header to your response from the server to not get such errors anymore. It can easily be done by adding this to your root file in server.

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  next();
});
Enter fullscreen mode Exit fullscreen mode

The * is a wildcard which allows all the origins (websites) to make requests to your server and it'll not throw anymore such CORS errors.

Access Control Allow Origin header in response must not be wildcard *

Well the problem is, if you're sending some credentials like cookies in your request, which means you have withCredentials: true (in axios) or credentials: 'include' (in fetch) then it'll again block the request with an error something like this.

Alt Text

This is my actual error message, if its not readable read below.

The value of the `Access-Control-Allow-Origin` header in the response must not be the wildcard `*` when the request's credentials mode is `include`. Origin is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Enter fullscreen mode Exit fullscreen mode

It means the server won't allow requests from all the origins when it gets specific credentials such as cookies from the user, so we get blocked by CORS, again.

Fix

Just add your frontend URL or any other website you want to have access to your API in place of *. If you have more than one, then feel free to comma separate it.

Response to preflight request doesn't pass access control check

What is Preflight?

A preflight request is made to see if CORS protocol is understood and whether it is safe to send the original requests. The requests such as DELETE, PUT or other methods that can amend data and having request headers that are not CORS-safelisted can make this preflight request. It is an OPTIONS request , using three HTTP request headers: Access-Control-Request-Method , Access-Control-Request-Headers, Origin refer this MDN article.

This is the error message which you'll get if your backend is not preflight enabled.

Alt Text

Fix

We can fix it easily by sending back the response Access-Control-Allow-Methods header with all the allowed HTTP methods and a response status of 200 , upon getting an OPTIONS request. So let's add to our middleware.

app.use((req, res, next) => {
  if (req.method === "OPTIONS") {
    res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, GET");
    return res.status(200).json({});
  }
  next();
});
Enter fullscreen mode Exit fullscreen mode

Access Control Allow Credentials header in response is ' ' which must be 'true' when the request credentials mode is 'include'

Access Control Allow Credentials is also a header that needs to be present when your app is sending requests with credentials like cookies, i.e. you have withCredentials: true (in axios) or credentials: 'include' (in fetch). This is the message you get upon not having this header and sending credentials along with request.

Alt Text

Fix

You can add this header along with other headers as shown above.

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Credentials", true);
  next();
});
Enter fullscreen mode Exit fullscreen mode

Pro Tip

If you're using express/connect then you have a ready made Node.js CORS middleware package that does this exact thing of adding headers for you in a convinient way. You can install it with, npm install cors.

As said it is so easy to setup, if you only need basic cors features enabled you can just write.

const cors = require("cors");
app.use(cors());
Enter fullscreen mode Exit fullscreen mode

It is also configurable, but the default config is:

{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "preflightContinue": false,
  "optionsSuccessStatus": 204
}
Enter fullscreen mode Exit fullscreen mode

So by default your:

  • Access Control Allow Origin is *
  • Access Control Allow Methods is GET,HEAD,PUT,PATCH,POST,DELETE
  • Pass the CORS preflight response to the next handler, false.

You can configure it according to your apps needs, here is the list of available options.

This is how I chose to do for my app.

const origin =
  process.env.NODE_ENV === "production"
    ? process.env.FRONTEND_PROD_URL
    : process.env.FRONTEND_LOCAL_URL;

// Setting up cors
app.use(
  cors({
    origin: origin,
    preflightContinue: true,
    methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
    credentials: true,
  })
);
Enter fullscreen mode Exit fullscreen mode

The credentials key sets the Access-Control-Allow-Credentials to true. You can also do the same by adding each headers as we discussed above.

const origin =
  process.env.NODE_ENV === "production"
    ? process.env.FRONTEND_PROD_URL
    : process.env.FRONTEND_LOCAL_URL;

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", origin);
  res.header("Access-Control-Allow-Credentials", true);

  if (req.method === "OPTIONS") {
    res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, GET");
    return res.status(200).json({});
  }
  next();
});
Enter fullscreen mode Exit fullscreen mode

My app's still showing CORS issues in console but don't know whats wrong

This happened to me, I only used MSFT Edge primarily and Firefox for testing so in both browsers my app worked fantastically. But the people I gave to check my app complained of getting a CORS error. It turns out all of them used Chrome which I haven't tested yet, so I grabbed Chrome and had a look into it, whose console still showed me the 2nd CORS issue we fixed above. What the heck!

Then after fiddling with the networks tab for a bit, a small warning ⚠️ symbol grabbed my attention which upon hover said,

A cookie associated with a cross-site resource at <url> was set without `SameSite` attribute. It has been blocked, as Chrome now delivers cookies with cross-site requests if they are set with `SameSite=none` and `Secure`.
Enter fullscreen mode Exit fullscreen mode

It turns out earlier this year, (February 2020) with release of Chrome 80 it has a secure by default cookie classification system, which needs a SameSite attribute on cookies to be accessible by the browser. It has three values, Lax, Strict, None and you have to decide which one should your cookie use depending upon freedom you want to give.

After googling a shit ton, this article by heroku came up, Chrome's Changes Could Break Your App: Prepare for SameSite Cookie Updates which explained why we need this and how to add this attribute.

So as you're here, I'll say you how I fixed this.

Fix

I used one package express-session which is a simple session middleware to handle creating session and storing in MongoDB with connect-mongo plugin. You can configure it similar to the cors package for your apps requirements.

So, all I had to do was add a sameSite attribute to it's cookie settings and it worked perfectly.

const session = require("express-session");

const sessionConfig = {
  // ... other methods
  cookie: {
    sameSite: "none",
  },
};

if (process.env.NODE_ENV === "production") {
  app.set("trust proxy", 1); // trust first proxy
  sessionConfig.cookie.secure = true; // serve secure cookies
}

app.use(session(sessionConfig));
Enter fullscreen mode Exit fullscreen mode

I took care that the secure property must be true only in production environment, which means only origins with HTTPS can access the cookies. And trust proxy is 1 which it trusts the first hop from front-facing proxy server. To know more, refer docs on trust-proxy.

Summary ✨

CORS is really important and useful for protecting your users from CSRF attacks and similarly the new updated policy on Same Site attributes by Google is helpful. Although it may seem frustrating upon getting these set of errors constantly for two long days (which I did), in the end I got to know so many aspects of making a secure server and safe authentication which was worth it in the end.

Feel free to check the project I build which is an Authentication app, I made this to learn local and OAuth strategies using Passport and Sessions. You can find the source code on my GitHub.

Discussion

pic
Editor guide
Collapse
sqlrob profile image
Robert Myers

Some things I found while trying to work out CORS headers:

  • Start with Chrome first. Its logging to console around CORS is a LOT better than Firefox
  • Test in private mode or clear caches after a failure. Anything CORS related is cached too.
Collapse
chrstnfrrs profile image
Christian Farris

I spent so much time trying to figure out the last one:

A cookie associated with a cross-site resource at <url> was set without `SameSite` attribute. It has been blocked, as Chrome now delivers cookies with cross-site requests if they are set with `SameSite=none` and `Secure`

The solution you posted didn't work. We had to put an SSL certificate on the API to get everything to work.

Collapse
heytulsiprasad profile image
Tulsi Prasad Author

Yeah you'd have to when you're signing secure as true. In my case, SSL was by default when I deployed to Heroku so I didn't get that issue. Sorry for you! I'm still skeptic if its a boon or bane, as other browsers didn't have it on yet!

Collapse
nitinkatageri profile image
Niitn Katageri

Thank you for this article, the exact set of issues I too fixed recently. I did have few questions which I haven't found answers for yet, maybe you can share your experience,

Would adding the origin from the request header in Access-Control-Allow-Origin make an API any less secure? I was inclined towards this when an API I was developing would be used by multiple client applications.

Should the CORS related headers be added only for browser originated requests? Any security issues if they are present for non-browser requests too?

Collapse
zdev1official profile image
ZDev1Official

Wow!
That helped me a lot!

Collapse
gbenga profile image
Gbenga Ojo-Aromokudu

Wish I had seen this when I was first using Rails - super helpfull!

Collapse
heytulsiprasad profile image
Tulsi Prasad Author

Yes man, I too wanted it in first place, that was my reason behind writing this 🙂