Search code examples
node.jsreactjscorspassport.jspassport-google-oauth

No 'Access-Control-Allow-Origin' header is present on the requested resource. React App Heroku API


I'm trying to integrate passport-google-oauth20 in my MERN application and although everything works fine in development on my local host but in production, it keeps throwing this error;

No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

I have searched and gone through multiple stack overflow posts and tried some of the answers and suggestions but nothing seems to work. This my index.js and the codes commented out are some of the solutions and CORS settings I've tried. I also tried putting the URL directly instead of an environment variable, but that didn't work either.

const express = require("express");
const cookieSession = require("cookie-session");
const passport = require("passport");
const cors = require("cors");
const helmet = require("helmet");
const connectDB = require("./config/db");
const authRoute = require("./routes/auth.route");
const userRoute = require("./routes/user.route");
const adminRoute = require("./routes/admin.route");
const transactionRoute = require("./routes/transaction.route");

//Passport setup
require("./passport");

const path = require("path");

// require("dotenv").config();
const app = express();

require("dotenv").config({
  path: "./config/config.env",
});

//Connect to Database
connectDB();

//Use bodyParser
app.use(express.json());

app.use(
  helmet({
    contentSecurityPolicy: false,
    frameguard: true,
  })
);

app.use(
  cookieSession({
    name: "session",
    keys: ["ccurves"],
    maxAge: 24 * 60 * 60 * 100,
  })
);

app.use(passport.initialize());
app.use(passport.session());

//Config for only development
// if (process.env.NODE_ENV === "development") {
//   app.use(
//     cors({
//       origin: process.env.CLIENT_URL,
//       methods: "GET,POST,PUT,DELETE",
//       credentials: true,
//     })
//   );
// }

// const corsOptions = {
//   origin: [`${process.env.CLIENT_URL}`],
//   methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
//   allowedHeaders: [
//     "Access-Control-Allow-Headers",
//     "Origin",
//     "X-Requested-With",
//     "Content-Type",
//     "Accept",
//     "Authorization",
//     "token",
//     "Access-Control-Request-Method",
//     "Access-Control-Request-Headers",
//     "Access-Control-Allow-Credentials",
//   ],
//   credentials: true,
//   preflightContinue: false,
//   optionsSuccessStatus: 204,
// };
// app.use(cors(corsOptions));

app.use(
  cors({
    origin: process.env.CLIENT_URL,
    methods: "GET,POST,PUT,DELETE",
    credentials: true,
  })
);

// app.use((req, res, next) => {
//   res.header("Access-Control-Allow-Origin", `${process.env.CLIENT_URL}`);
//   res.header("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
//   res.header("Access-Control-Allow-Credentials", true);
//   res.header(
//     "Access-Control-Allow-Headers",
//     "Access-Control-Allow-Headers, Origin, X-Requested-With, Content-Type, Accept, Authorization, token, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Allow-Credentials, Access-Control-Allow-Origin"
//   );

//   next();
// });

app.use("/api/auth", authRoute);
app.use("/api/user", userRoute);
app.use("/api/admin", adminRoute);
app.use("/api/transaction", transactionRoute);

const port = process.env.PORT;

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

And in my react frontend I'm fetching the request from the API like this:

 const getUser = () => {
      fetch(`${process.env.REACT_APP_API_URL}/auth/login/success`, {
        method: "GET",
        credentials: "include",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          "Access-Control-Allow-Credentials": true,
        },
      })
        .then((response) => {
          if (response.status === 200) return response.json();
          throw new Error("authentication has been failed!");
        })
        .then((resObject) => {
          authenticate(resObject, () => {
            isAuth && navigate("/");
          });
        })
        .catch((err) => {
          console.log(err);
        });
    };

I also tried using axios to send the request,

   axios
        .get(`${process.env.REACT_APP_API_URL}/auth/login/success`, {
          withCredentials: true,
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
          },
        })
        .then((res) => {
          console.log(res);
          authenticate(res.data, () => {
            isAuth && navigate("/");
          });
        })
        .catch((err) => {
          console.log(err);
          // setError(err.response.data.errors);
        });

And it works fine and perfectly on my local host, all other routes are working and even the other authentication method works fine. Why is this particular route been blocked by CORS? When I open the API URL ${process.env.REACT_APP_API_URL}/auth/login/success directly in the browser I can see the json data is been sent fine. What is going on? Please what am I doing wrong?


Solution

  • @Kaneki21 Solution actually worked for me but he has deleted his answer so I'm reposting it. All credits go to him, so this cors configuration solved the error:

    const corsOptions = {
      origin: [`${process.env.CLIENT_URL}`],
      methods: "GET,HEAD,PUT,OPTIONS,POST,DELETE",
      allowedHeaders: [
        "Access-Control-Allow-Headers",
        "Origin",
        "X-Requested-With",
        "Content-Type",
        "Accept",
        "Authorization",
        "token",
        "Access-Control-Request-Method",
        "Access-Control-Request-Headers",
        "Access-Control-Allow-Credentials",
      ],
      credentials: true,
      preflightContinue: false,
      optionsSuccessStatus: 204,
    };
    app.use(cors(corsOptions));
    

    But I ran into another issue, Although the request was sent now, I noticed cookies weren't included in the header even after I added withCredentials: true. Inspecting the request in the chrome network tab I could see the cookies was been filtered out. I solved that by switching from cookie-session to express-session and using this config:

    app.set("trust proxy", 1); // trust first proxy
    app.use(
      session({
        secret: process.env.JWT_SECRET,
        resave: false,
        saveUninitialized: true,
        cookie: {
          secure: true,
          sameSite: "none",
        },
      })
    );
    

    Notice the sameSite: "none" in the cookie configuration, that enables cross-sites request and it requires secure to be true. Previously cookie-session wasn't setting the sameSite attribute even when I included it in the cookie config and browsers set Lax as a default so hence the switch to express-session. You can check out these sites to understand more site1 site2

    I hope this helps someone out cause I know this gave me quite a bit of a headache