We help IT Professionals succeed at work.

Why Does This Make a Difference?

Bruce Gust
Bruce Gust asked
on
Here's my initial code:

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, PATCH, DELETE'
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  next();
});

Open in new window


This is part of a Node.js tutorial I'm going through. It's a React front end that interacts with an API which utilizes GraphQL.

The code that I've got above is from the "app.js" file that's a part of the API. I've got the code in its entirety below:

const path = require("path");

const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const multer = require("multer");
const graphqlHttp = require('express-graphql');

const graphqlSchema = require('./graphql/schema');
const graphqlResolver = require('./graphql/resolvers');

const app = express();

const fileStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, "images");
  },
  filename: (req, file, cb) => {
    //cb(null, uuidv4());
    cb(null, file.originalname);
  }
});

const fileFilter = (req, file, cb) => {
  if (
    file.mimetype === "image/png" ||
    file.mimetype === "image/jpg" ||
    file.mimetype === "image/jpeg"
  ) {
    cb(null, true);
  } else {
    cb(null, false);
  }
};

app.use(bodyParser.json());
app.use(
  multer({
    storage: fileStorage,
    fileFilter: fileFilter
  }).single("image")
);
app.use("/images", express.static(path.join(__dirname, "images")));

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, PATCH, DELETE'
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
  if(req.method==='OPTIONS') {
	  return res.sendStatus(200);
  }
  next();
});

app.use(
	'/graphql', 
	graphqlHttp({
		schema: graphqlSchema, 
		rootValue: graphqlResolver, 
		graphiql:true, 
		formatError(err) {
			if(!err.originalError) {
				return err;
			}
			const data = err.originalError.data;
			const message = err.message || 'An error occurred.';
			const code = err.originalError.code || 500;
			return { message: message, status: code, data: data };
		}
	})
);

app.use((error, req, res, next) => {
  console.log(error);
  const status = error.statusCode || 500;
  const message = error.message;
  const data = error.data;
  res.status(status).json({ message: message, data: data });
});

mongoose
  .connect(
    "my database"
  )
  .then(result => {
    const server = app.listen(8080);
  })
  .catch(err => console.log(err));

Open in new window



Everything about the app is sound, as far as there being no syntactical errors. But every time I attempted to set up a user, I would get this error:

Access to fetch at 'http://localhost:8080/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

After researching what CORS was and trying different things, I downloaded the sample code from the tutorial and was able to identify this one line as being the "thing" that made all the difference. I've got it in bold below:

app.use((req, res, next) => {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PUT, PATCH, DELETE'
  );
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
[b] if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }[/b]
  next();
});

Open in new window


At that point, the error went away.

Why?
Comment
Watch Question

ZvonkoSystems architect
Top Expert 2006

Commented:

Extend this:   Access-Control-Allow-Methods

Add: OPTIONS

Bruce GustPHP Developer

Author

Commented:
OK, I was able to get some more information.

Apparently, GraphQL automatically denies anything that isn't a GET or a POST request. Hence, anything that falls into the category of "options" is processed as a problem.

By including the aforementioned IF statement and returning a status code of 200, we're able to circumvent all of that.

But if I'm adding a user, is that not a POST request? Why do "OPTIONS" solve the problem?
Systems architect
Top Expert 2006
Commented:

CORS is realised  in the modern browsers by sending OPTIONS before the real  POST request to see the headers like  "Access-Control-Allow-Origin"
If OPTIONS returns headers that do not allow POST then is POST denied.

Most Valuable Expert 2018
Distinguished Expert 2019
Commented:
Hey Bruce,

By default, JS can't access data from a different server (domain / scheme or port), so when accessing remote data, you need to configure the remote server to allow CORS (Cross Origin Resource Sharing).

When an app needs to make a request to a different server, it will run what's called a Pre-Flight request. Think of this as a mini request - your app automatically sends an OPTIONS request to the server containing some headers, indicating who's making the request (Origin) and what method it's planning to send (GET / POST etc).

If the server responds with the correct headers (Access-Control-Allow-Origin sets up who can make the request / Access-Control-Allow-Methods sets up what methods are allowed - POST / GET etc), then your app will continue on and make the request that you intended.

In your code above, you're setting those headers to allow a CORS request from anywhere (Access-Control-Allow-Origin", "*") and to allow POST, PATCH, PUT, GET, DELETE requests. You're then checking to see if the request is a Pre-Flight request (OPTIONS) and if is, you're returning those headers back to your app. Your app then knows what request methods it can respond to and from where.
Most Valuable Expert 2017
Distinguished Expert 2019
Commented:
Some background on why this happens.

It is basically done for security. JavaScript / Browser code is very insecure - it is open text, and it can be manipulated.

Let's say you have a super secret API that you don't want to make available to the public but require people to subscribe. This is a REST API that provides useful information. To protect it you wrap it in some security like OAUTH etc.

Let's say someone wants to use that from the browser. To access your site they would need to put in a username and password as part of the request - but if this is in the browser then that username and password is visible to the world and boom there goes your revenue stream.

So what you do is deny CORS access - which basically boils down to preventing AJAX requests from browsers. This forces developers to proxy the call to your service by creating a server side script that makes the authorised call to your server and returns that data to its own script - thereby hiding the sensitive bits.

The browser assumes that if the request is to the same domain / protocol / port that you know what you are doing and allows the request. In all other instances you have to expressly tell the browser who is allowed to access the resource.

In your case you have said everyone '*', but you can be more restrictive and only specify certain addresses that are allowed through.

So, given that you are master of both client and server in this case - why the CORS error - and why did the fix work
Access to fetch at 'http://localhost:8080/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Reason being the Port you are querying (8080) does not match the port you loaded the script from (3000) - even though the server and protocol are the same - different port means different service so it will result in a CORS error if access has not been granted from the service on 8080.
ZvonkoSystems architect
Top Expert 2006

Commented:

Hello Bruce, you handled the CORS Request Methot OPTIONS yourself in your app.

For that is a npm package: cors


I got this info from here:
https://medium.com/@dvelasquez/handle-an-options-request-with-cors-in-node-js-f3f81c5a7494



Bruce GustPHP Developer

Author

Commented:
Excellent, gentlemen!

Thank you!