Search code examples
reactjsmernexpress-sessionpassport-localrender.com

passport-local's deserializeUser() is not called during deployment. Works fine in development


(Yes, I know class components are outdated, updating to functional/hooks after successful deployment)

I have implemented a MERN (MongoDB, Express, React, and Node.js) application with authentication using Passport's local strategy and ES6.

Expected: When the user logs in, they send a request to the "/auth/login" route, which is authenticated using the passport.authenticate() method. If the authentication is successful, the server sends a "success" response back to the client.

Once the client receives the "success" response, the App.js component's componentDidMount() method is called. In this method, the client sends a request to the "/auth/test" route to check if the user is authenticated using req.isAuthenticated().

If the user is authenticated, the server sends back the user object, which is then stringified and stored in the client's local storage to indicate that the user is logged in.

Development Mode: For development, I am having the client on one port and the API on another. Everything works fine.

Currently: After deployment, I am hosting both the client and API through Render.com.

  • I have ensured that all the environment variables are set properly within Render
  • I have updated any baseURL to match the new client and API URLs respectively as I move away from localhost.
  • I have put console.log's everywhere to ensure the flow is as expected (it's not. See below).

Current Flow:

  • serializeUser() is called with the correct id.
  • localStrategy is called and it finds the correct user and password and returns done(null, userObj).
  • /auth/login returns the expected status.
  • componentDidMount() fires in App.js.
  • /auth/test (in componentDidMount()) fires and this is where we check req.isAuthenticated.

Notes on CORS:

  • I am not receiving any errors/warnings about CORS
  • I am opting in from the client with credentials: "include"
  • I am opting in from the server with
    const allowedOrigins =
      process.env.NODE_ENV === "development"
        ? "http://localhost:3000"
        : "https://my-client.onrender.com";
    const corsOptions = {
      origin: allowedOrigins,
      credentials: true,
    };
    app.use(cors(corsOptions));
    

Problem: req.isAuthenticated() returns false because req is undefined.

What I've witnessed (through console logs):

  • deserializeUser() is never called (Gets called fine in dev)
  • Cookies are never set during deployment (Cookie sets fine in dev)
  • There are no errors within the API logs
  • I get no errors on the console

Thank you

Client

/auth/login

    const url = baseURL + "/auth/login?username=" + id + "&password=" + pw;
    const res = await fetch(url, {
      method: "POST",
      credentials: "include",
    });

/auth/test

     fetch(baseURL + "/auth/test", {
        credentials: "include",
      })

API

/auth/login Route

authRoute.post("/auth/login",passport.authenticate("local"),
  (req, res) => {
    res.status(200).send("Login successful");
  },

/auth/test Route

authRoute.get("/auth/test", (req, res) => {
  const isAuth = req.isAuthenticated(); // PROBLEM: Returns false because req is undefined
// etc.

passportConfig

  app
    .use(
      session({
        secret: process.env.SESSION_SECRET,
        resave: false,
        saveUninitialized: true,
        cookie: {
          secure: process.env.NODE_ENV === "development" ? false : true,
          httpOnly: process.env.NODE_ENV === "development" ? false : true,
          sameSite: process.env.NODE_ENV === "development" ? "" : "none", // Set if using CORS
          domain: process.env.NODE_ENV === "development" ? "" : ".onrender.com",
          path: "/",
          maxAge: 1000 * 60 * 5,
        }, // 5 minutes
      })
    )

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

Thanks in advance!


Solution

  • One thing that looks suspect in your configuration is this line, specifically the . preceding your domain:

    domain: process.env.NODE_ENV === "development" ? "" : ".onrender.com", line.

    I would drop that line entirely. You could also change it to just onrender.com.

    Express will also not set a cookie if the remote server is passing the request through a non-http intermediary. You can tell it to trust this proxy:

    if (process.env.NODE_ENV === 'production') {
      app.set('trust proxy', 1);
    }