skip to Main Content

I have an app that’s built on react/next and a server built on express.

The basic flow is that a user authenticates via Github Oauth using passport.js, the server then returns a connect.sid cookie which should be used on subsequent requests to the server in order to render data to an authenticated user when they hit a protected endpoint.

As of now I can hit my authentication endpoint in the browser to obtain the cookie. And when I use the cookie in postman by adding it to the headers everything works as intended and the requested data is returned. Great!

The problem is now integrating it with a react/next app.

After authenticating I can see in the application tab that no connect.sid cookie is present, until after I fetch another protected endpoint, then it shows up. Not sure this is relevant or if it’s just not updating immediately.

browser cookies

When a fetch is triggered I notice in the network tab that there is no connect.sid cookie present in the header at all, and I’m not sure how to force the fetch to include it.

Here is my fetch

fetch(`api.invoice-app.naughty-cat.com/invoices/all`, {
  withCredentials: true,
  credentials: 'include'
})
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.log(err));

I have a suspicion it is related to either my cors config or session config, I have tried many combinations of settings without luck yet. I am sure I am missing some fundamental understanding about something here.


app.use(
  cors({
    credentials: true,
    origin: '*'
  })
);

app.use(
  session({
    secret: process.env.EXPRESS_SESSION_SECRET,
    resave: false,
    proxy: true,
    saveUninitialized: false,
    samesite: 'none',
    secure: false,
    cookie: { maxAge: 1000 * 60 *60 * 24
    },
    store: sessionStore
  })
);

2

Answers


  1. Chosen as BEST ANSWER

    So I was able to finally figure out the proper settings with help from comments, answers, and other posts. I'll post things here in case it helps someone else in the future.

    First here is the fetch, making sure to include credentials:

    fetch(`http://localhost:3001/invoices/all`, {credentials: 'include'})
    .then(response => response.json())
    .then(response => console.log(response))
    .catch(err => console.log(err))
    

    Here is my cors setup":

    app.use(
      cors({
        //Allows session cookie from browser to pass through
        credentials: true, 
        //Sets the allowed domain to the domain where the front end is hosted, this could be http://localhost:3000 or an actual url
        origin: process.env.FRONT_END_URL
      })
    );
    

    And here are my session settings, the meat and potatoes of the post:

    app.use(
      session({
        secret: process.env.EXPRESS_SESSION_SECRET,
        resave: false,
        proxy: true,
        saveUninitialized: false,
        httpOnly: true,
        cookie: { 
          sameSite: 'lax',
          secure: process.env.NODE_ENV === "production" ? "true" : process.env.NODE_ENV === "development" ? "auto" : "true",
          maxAge: 1000 * 60 *60 * 24
        },
        store: sessionStore
      })
    );
    
    

    The trick for me was the cookie settings depending on where things are being hosted.

    When the server is hosted on some external domain, but the front end is on localhost for development then sameSite: "none" and secure: true settings will allow the cookie to be sent in fetches (though I think this will soon be disallowed by browsers as they are all blocking third party cookies soon)? For this reason I have left it out.

    When the server and the front end are both hosted on localhost then the settings sameSite: lax and secure: auto will work (since you likely wont have ssl on localhost).

    When the server and front end are both hosted on the same domain (i.e. for production) then sameSite: lax and secure: true are the settings you will likely be wanting to allow the cookies to be passed.

    I have a check in my code now for the environment (dev or prod) which sets things depending on that.


  2. I see possible two issues here.

    1. There is a typo in your fetch request. You have written withCredntials. It should be withCredentials
    fetch(`http://localhost:3001/invoices/all`, {
      withCredentials: true, // Corrected from withCredntials to withCredentials
      credentials: 'include'
    })
    
    1. Your CORS configuration in the Express app is set to origin: '*'. This setting does not work when credentials are included in cross-origin requests. You must specify an explicit origin instead of using *. For local development, this should be the URL of your React/Next.js app, for example, http://localhost:3000. Update your Express server’s CORS configuration like below
    app.use(
      cors({
        credentials: true,
        origin: 'http://localhost:3000' // The exact URL of your React/Next.js app
      })
    );
    

    UPDATE

    There is no withCredentials option inside the fetch(). It should be only credentials:"include". Look at this Link

    So your fetch() should be

    fetch(`http://localhost:3001/invoices/all`, {
      credentials: 'include'
    })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search