Search code examples
javascriptnode.jsreactjscors

Access from origin 'https://example.com' has been blocked even though I've allowed https://example.com/


I have an app made with React, Node.js and Socket.io
I deployed Node backend to heroku , frontend to Netlify


I know that CORS errors is related to server but no matter what I add, it just cant go through that error in the picture below.
I also added proxy script to React's package.json as "proxy": "https://googledocs-clone-sbayrak.herokuapp.com/"

corserror

And here is my server.js file;

const mongoose = require('mongoose');
const Document = require('./Document');
const dotenv = require('dotenv');
const path = require('path');
const express = require('express');
const http = require('http');
const socketio = require('socket.io');
dotenv.config();

const app = express();
app.use(cors());
const server = http.createServer(app);
const io = socketio(server, {
  cors: {
    origin: 'https://googledocs-clone-sbayrak.netlify.app/',
    methods: ['GET', 'POST'],
  },
});

app.get('/', (req, res) => {
  res.status(200).send('hello!!');
});

const connectDB = async () => {
  try {
    const connect = await mongoose.connect(process.env.MONGODB_URI, {
      useUnifiedTopology: true,
      useNewUrlParser: true,
    });

    console.log('MongoDB Connected...');
  } catch (error) {
    console.error(`Error : ${error.message}`);
    process.exit(1);
  }
};

connectDB();


let defaultValue = '';

const findOrCreateDocument = async (id) => {
  if (id === null) return;

  const document = await Document.findById({ _id: id });

  if (document) return document;

  const result = await Document.create({ _id: id, data: defaultValue });
  return result;
};
io.on('connection', (socket) => {
  socket.on('get-document', async (documentId) => {
    const document = await findOrCreateDocument(documentId);
    socket.join(documentId);
    socket.emit('load-document', document.data);
    socket.on('send-changes', (delta) => {
      socket.broadcast.to(documentId).emit('receive-changes', delta);
    });

    socket.on('save-document', async (data) => {
      await Document.findByIdAndUpdate(documentId, { data });
    });
  });
  console.log('connected');
});

server.listen(process.env.PORT || 5000, () =>
  console.log(`Server has started.`)
);

and this is where I make request from frontend;

import Quill from 'quill';
import 'quill/dist/quill.snow.css';
import { useParams } from 'react-router-dom';
import { io } from 'socket.io-client';

const SAVE_INTERVAL_MS = 2000;
 

const TextEditor = () => {
  const [socket, setSocket] = useState();
  const [quill, setQuill] = useState();
  const { id: documentId } = useParams();

  useEffect(() => {
    const s = io('https://googledocs-clone-sbayrak.herokuapp.com/');
    setSocket(s);
     

    return () => {
      s.disconnect();
    };
  }, []); 

 /* below other functions */
 /* below other functions */
 /* below other functions */
 }


Solution

  • TL;DR

    Web origins do not contain a path.

    Accordingly, https://googledocs-clone-sbayrak.netlify.app/ is not a valid Web origin. Drop that trailing slash.

    More details about the problem

    No trailing slash allowed in the value of the Origin header

    According to the CORS protocol (specified in the Fetch standard), browsers never set the Origin request header to a value with a trailing slash. Therefore, if a page at https://googledocs-clone-sbayrak.netlify.app/whatever issues a cross-origin request, that request's Origin header will contain

    https://googledocs-clone-sbayrak.netlify.app
    

    without any trailing slash.

    Byte-by-byte comparison on the server side

    You're using Socket.IO, which relies on the Node.js cors package. That package won't set any Access-Control-Allow-Origin in the response if the request's origin doesn't exactly match your CORS configuration's origin value (https://googledocs-clone-sbayrak.netlify.app/).

    Putting it all together

    Obviously,

    'https://googledocs-clone-sbayrak.netlify.app' ===
    'https://googledocs-clone-sbayrak.netlify.app/'
    

    evaluates to false, which causes the cors package not to set any Access-Control-Allow-Origin header in the response, which causes the CORS check to fail in your browser, hence the CORS error you observed.

    Example from the Fetch Standard

    Section 3.2.5 of the Fetch Standard even provides an enlightening example of this mistake,

    Access-Control-Allow-Origin: https://rabbit.invalid/
    

    and explains why it causes the CORS check to fail:

    A serialized origin has no trailing slash.