-1

I was testing get and post requests in jsfiddle to better understand cors and csrf.

fetch('http://localhost:5000/auth')
  .then((response) => response.json())
  .then((data) => {
    console.log('Success:', data);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

chrome console get request

Where in the code is an OPTIONS request handled with a status code of 200? I don't have any catch all middleware.

const app = express()

//Listen
//Connect to database

//Middlewares
app.use(express.json())

//Routes
app.use('/auth', require('./routes/auth'))
const router = express.Router()

router.get('/', async (req, res) => {
  console.log('test')
  res.status(202).send(await User.find())
})

router.post('/signup', async (req, res) => {
})

module.exports = router

I am having a hard time finding an OPTIONS route in the source code. Is express handling other routes as well by default?

Edit: More detailed images enter image description here

enter image description here

For context, when I added cors middleware, I was confused by why the options request had a status code of 204. Then, I removed the cors middleware to see what would happen and was surprised that a response was being sent, which made me wonder what other things are handled by default.

Edit 2: Tried a simpler test without the json middleware in case it did something:

const express = require('express')

const app = express()

app.get('/', (req, res) => {
  res.send('Hi')
})

app.listen(3001, () => {
  console.log('Listening on port', 3001)
})

enter image description here

enter image description here

Edit 3: Used a simple webpage instead of jsfiddle:

<script>
    const data = { name: 'example', password: 'password'};

    fetch('http://localhost:3001', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    })
      .then((response) => response.json())
      .then((data) => {
        console.log('Success:', data);
      })
      .catch((error) => {
        console.error('Error:', error);
      });
  </script>

No preflight happens for get requests

enter image description here , but one does happen for a post request: enter image description here

enter image description here

  • What specific OPTIONS request URL do you think you're getting a response from with just this Express code? What exact response are you getting? – jfriend00 Dec 28 '22 at 07:18
  • 1
    FYI, your screenshot does not show what HTTP verb each request is. That complicates understanding what is going on here. – jfriend00 Dec 28 '22 at 08:23
  • It seems express [uses](https://github.com/expressjs/express/blob/158a17031a2668269aedb31ea07b58d6b700272b/lib/application.js#L168) final handler which [responds with 404](https://www.npmjs.com/package/finalhandler) out of the box. I didn't [find](https://github.com/expressjs/express/search?q=options+-path%3Atest) anything related to options requests. – Dropin' Science Dec 28 '22 at 21:16
  • 1
    Using jsFiddle to then send a request to localhost may be causing some unique problems because browsers may have special protections applied to localhost when the web page is not from localhost as that's an especially egregious security issue. – jfriend00 Dec 28 '22 at 21:23
  • Now I wonder why a preflight request was even sent in the first place for a get request (from localhost:5050 using a simple html page none is sent) where the [Content-type header](https://stackoverflow.com/a/41680210/10881655) wasn't changed. ("Content-Type: text/html; charset=utf-8") – Dropin' Science Dec 28 '22 at 21:58
  • CORS requires host AND port and protocol to match to avoid CORS enforcement. You appear to be sending from localhost:5050 to localhost:3001 which will require CORS. Your POST is triggering preflight because of `Content-Type: application/json` on the POST. What triggers pre-flight is ALL explained in the [MDN page](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) we pointed you to on your previous question. Only `application/x-www-form-urlencoded`, `multipart/form-data` and `text/plain` are allowed as content-type without pre-flight. – jfriend00 Dec 28 '22 at 22:01
  • Indeed a CORS error does happen, but I am just testing the 200 OPTIONS response (which happens even from localhost, see "Edit 3" above). When comparing the request headers from jsdfiddle and 127.0.0.1:5500/ the only difference I find is that localhost has a referrer header "Referer: http://127.0.0.1:5500/". The rest is identical, so I am confused why a GET request from jsfiddle would trigger a preflight request. – Dropin' Science Dec 28 '22 at 22:12
  • I no longer know exactly which GET request you're asking about. Precise code? Web page URL it's coming from? Exact headers being sent? – jfriend00 Dec 28 '22 at 22:43

1 Answers1

1

Where in the code is an OPTIONS request handled with a status code of 200? I don't have any catch all middleware.

Express apparently does have code that will, in some circumstances, send a 200 response back from an OPTIONS request. That code is here and here in the Express repository for further context. These two snippets of Express code are shown below here:

  // for options requests, respond with a default if nothing else responds
  if (req.method === 'OPTIONS') {
    done = wrap(done, function(old, err) {
      if (err || options.length === 0) return old(err);
      sendOptionsResponse(res, options, old);
    });
  }

And this (which sends a 200 response):

function sendOptionsResponse(res, options, next) {
  try {
    var body = options.join(',');
    res.set('Allow', body);
    res.send(body);
  } catch (err) {
    next(err);
  }
}

I found this by inserting this middleware into your Express server code and then setting a breakpoint inside the middleware and then stepping through the call to next() to see exactly what Express does in this case. And, low and behold, it eventually gets to sendOptionsResponse() and sends a 200 response for some OPTIONS requests.

// debugging middleware, should be first router handler of any kind
let requestCntr = 0;
app.use((req, res, next) => {
    let thisRequest = requestCntr++;
    console.log(`${thisRequest}: ${req.method}, ${req.originalUrl}, `, req.headers);
    // watch for end of theresponse
    res.on('close', () => {
        console.log(`${thisRequest}: close response, res.statusCode = ${res.statusCode}, outbound headers: `, res.getHeaders());
    });
    next();
});

FYI, you can use this above middleware to see exactly what is arriving on your server and in what order.

For your particular jsFiddle this is what I see:

Listening on port 3001
0: OPTIONS, /,  {
  host: 'localhost:3001',
  connection: 'keep-alive',
  pragma: 'no-cache',
  'cache-control': 'no-cache',
  accept: '*/*',
  'access-control-request-method': 'POST',
  'access-control-request-headers': 'content-type',
  'access-control-request-private-network': 'true',
  origin: 'https://fiddle.jshell.net',
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
  'sec-fetch-mode': 'cors',
  'sec-fetch-site': 'cross-site',
  'sec-fetch-dest': 'empty',
  'accept-encoding': 'gzip, deflate, br',
  'accept-language': 'en-US,en;q=0.9'
}
0: close response, res.statusCode = 200, outbound headers:  [Object: null prototype] {
  'x-powered-by': 'Express',
  allow: 'GET,HEAD,POST',
  'content-type': 'text/html; charset=utf-8',
  'content-length': '13',
  etag: 'W/"d-bMedpZYGrVt1nR4x+qdNZ2GqyRo"'
}

You can see there is exactly one inbound request to the server, an OPTIONS request. The express server (on it's own sends a 200 status, but only allows text/html as the content-typefor a GET, HEAD or POST. Since the jsFiddle wants to sentcontent-type: application/json`, the browser fails the request as a CORS violation.


NOTE and further explanation

As a point of confusion, the Chrome inspector (network tab) in the browser are showing more requests than actually show up on the server so you are being deceived by what Chrome is showing. When I run your client-side code in a JSFiddle, I get ONLY one OPTIONS request send to the server and NO other requests, even though the Chrome inspector shows more than that. What I think is happening is that Chrome sees the POST request as being content-type: application/json so it needs pre-flight. It sends the OPTIONS request as the pre-flight, it gets back a 200 response, but does NOT get back the headers it needs to allow that specific POST request to be sent so it doesn't send it.

When in doubt, instrument your server and see exactly what it is actually receiving.

Chrome deprecating direct access to private network endpoints

See this article. This may also be a reason why the public website jsFiddle can't access localhost or why there are extra warnings in the Chrome inspector. This is a fairly new restriction (in the last year). I've seen some other sites have to remove features because of this. For example, Tivo had to remove the ability to watch saved Tivo recordings (which are served from a local http server) from Chrome even when on my own local network. You can now only do that from an app.

jfriend00
  • 683,504
  • 96
  • 985
  • 979