-1

When using the following code:

SERVER:

const cors = require('cors')
const mongoose = require('mongoose');
const Pet = mongoose.model('pets');

const corsOptions = {
origin: 'http://localhost:3000'}

app.get(`/api/pets`, cors(corsOptions), async (req, res) => {
    let pets = await Pet.find();
    return res.status(200).send(pets);
});

app.post(`/api/pets`, cors(corsOptions), async (req, res) => {
    let pet = await Pet.create(req.body);
    return res.status(201).send({
        error: false,
        pet
    })
})

CLIENT:

import axios from 'axios';

export default {
    getAll: async () => {
        let res = await axios.get(`http://localhost:5000/api/pets`);
        return res.data || [];
    },
    post: async (name, birthdate, decription) => {
    let res = await axios.post(`http://localhost:5000/api/pets`, {
        name: name,
        birthdate: birthdate,
        description: decription
    });
        return res;
    },
    ...
}

The client is able to access the .get route just fine, but when it tries to .post I get the following error:

"Access to XMLHttpRequest at 'http://localhost:5000/api/test' from origin 'http://localhost:3000' 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."

Picture of headers

However, this will allow the .post route to work just fine:

SERVER:

const cors = require('cors')
...

const corsOptions = {
origin: 'http://localhost:3000'}

app.use(cors(corsOptions));

app.get(`/api/pets`, async (req, res) => {
    ...
});

app.post(`/api/pets`, async (req, res) => {
    ...
})

What is the difference between the two approaches, and why does using "app.use(...)" allow the .post route to work but not the other?

Jefumaru
  • 1
  • 2
  • 1
    Chances are there is something in your POST request that is triggering a pre-flight request (an OPTIONS request) which you do not have a handler for. If you want us to help you with that, we would need to see exactly what the POST request is including content-type and custom headers. – jfriend00 Nov 21 '20 at 19:06
  • I've edited the post to include exactly what I have, and I've also added what's on the client side as well. "Pet" is a mongoose model – Jefumaru Nov 21 '20 at 20:02
  • Is there any specific reason you want to have CORS only on some URL but not others? – slebetman Nov 21 '20 at 20:08
  • Because I only want it to accept requests from that URL. I'm hoping to deploy this one day and I want at least some security on it – Jefumaru Nov 21 '20 at 20:11
  • We need to see the ENTIRE code for sending the request from the client OR the actual POST request (including all headers). As I said above, I suspect you have something in the POST request that triggers CORS preflight which you don't have a handler for. But, seeing just a shell of the code and no actual view of the request itself means we can't tell for sure. Certain content types or certain header on the request can trigger the preflight. FYI, if you look at the network tab in the Chrome debugger, you can see EXACTLY what is happening. We are all flying blind right now. – jfriend00 Nov 21 '20 at 20:14
  • I edited the post to add a picture of the headers I see from the network tab in the Chrome debugger. I've also added the rest of the post code from the client – Jefumaru Nov 21 '20 at 20:36

2 Answers2

0

You have to explicitly allow method post in your allowed http methods.

So your cors options should be something like:

const corsOptions = {
  "origin": "http://localhost:3000",
  "methods": "GET,POST", //here explicitly allowing http post method
  "preflightContinue": false,
  "optionsSuccessStatus": 204}

Here is the documentation for cors lib, you can just check more options.

Danizavtz
  • 2,845
  • 4
  • 25
  • 22
-2

All modern day browser send a pre-flight request (a request sent before sending post or get request) to implement same-origin policy. Severs are expected to reply back with the domain that can access the sent response. In first case, you are not sending any pre-flight response in your response. When you use app.use(cors(corsOptions));, your express server will add "origin": "*" in pre-flight request, which means that all domains can access response sent by server. As to why get works and post doesn't: Any request — including any GET request — which contains a header that's not among those CORS-safelisted request-headers listed above will trigger a preflight. Meaning your get request header may be CORS-safelisted request-headers.

More info can be found here: Preflight request is sent with all methods

and here: https://medium.com/@dtkatz/3-ways-to-fix-the-cors-error-and-how-access-control-allow-origin-works-d97d55946d9

I am adding additional info to explain why first case doesn't work and second works:

Let me first say, when CORS requests are sent. CORS requests(or preflight requests) are sent only when requests are not "simple requests", you can see what a simple request is here.

Now, whenever browser encounters a request which is not a simple request, it sends a preflight request(HTTP method: options) before sending an actual request(get/post or any other method). In first case, get is working because it falls into 'simple request' category. But post doesn't work because browser will send an OPTIONS preflight request before sending an actual POST request(since POST is not a simple request - refer to the link above as to why). Since in first case you've only implemented cors for post and get, OPTIONS request is not handled by server and so browser will not send a POST request.

In second case, you've used app.use(), which will include CORS headers in any request that server receives and so your preflight OPTION request is handled by app.use(), which allows localhost:3000 as an origin.

G Patel
  • 117
  • 1
  • 3
  • 7
  • Do you really think that all browsers send pre-flight on all POST requests? That part of your post doesn't seem right to me. – jfriend00 Nov 21 '20 at 20:25
  • But I've defined my origins in the corsOptions as const corsOptions = { origin: 'http://localhost:3000'} – Jefumaru Nov 21 '20 at 20:40
  • @Jefumaru I've edited the response, see if it explains things, if so I'll format it better. – G Patel Nov 22 '20 at 20:19
  • So POST is not a simple request...which means it shouldn't be triggering a preflight request...right? – Jefumaru Nov 23 '20 at 23:31
  • No.. since POST is NOT a simple request, it will trigger preflight. that's what I've mentioned. – G Patel Nov 24 '20 at 05:15