I'm making a web app. My back end uses Node.js, Express.js, and specifically, it uses the module express-session to create a session object for session-based authentication in order to persist a user login. I understand that when I use express-session to create a session, a cookie with the session ID is created on the back end and sent to the browser on the front end. I have verified that this cookie sends seamlessly when I use my browser and visit the page the Express app is hosted on (in my case, localhost:3001
).
// My Express app's index.js file
// This code seamlessly sends a session ID cookie to the browser when I visit http://localhost:3001
const express = require('express');
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const app = express();
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/GameDB',
collection: 'UserSessions'
});
store.on('error', (err) => {
console.log('Something exploded:' + err);
});
app.use(session({
secret: 'I am stuck, help me please!',
store: store,
saveUninitialized: true,
resave: false,
} ));
app.listen(3001, () => {
console.log('server started on 3001');
})
And I get my cookie just fine. Here's a screenshot in my Chrome developer tools:
However, I want my front end to be a separate app (I'm using React), and I want to use API requests from my front end to access everything on my back end. As the front end and back end are separate apps, they will need to run on different ports. My front end will run on port 3000 and my back end will continue to run on port 3001. And with that in mind, I'll be running localhost:3000
in my browser instead of localhost:3001
.
The only problem is, with these changes, the cookie made by express-session no longer gets sent to my browser, even when I do an HTTP POST (via JavaScript fetch()
) from my front end to my back end and get a valid response back.
In a nutshell, my question is: how can I have my express-session session ID cookie saved to my browser when I'm using a separate app for the front end?
Here's my front end API request:
fetch('http://localhost:3001/gimmecookie', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ data: "data" })
})
.then(response => response.json() )
.then(response => {
console.log(response)
})
.catch((error) => {
console.error(error);
});
And here's the slightly-modified-from-before index.js file for my Express app:
// My Express app's index.js file that *should* create and
// send a session-id cookie to my React project
const express = require('express');
const cors = require('cors');
const session = require('express-session');
const mongoDbStore = require('connect-mongodb-session')(session);
const app = express();
const corsOptions = {
origin: 'http://localhost:3000',
}
const store = new mongoDbStore({
uri: 'mongodb://localhost:27017/GameDB',
collection: 'UserSessions'
});
store.on('error', (err) => {
console.log('Something exploded:' + err);
});
app.use(cors(corsOptions));
app.use(session({
secret: 'I am stuck, help me please!',
store: store,
saveUninitialized: true,
resave: false,
}));
app.post('/gimmecookie', (req, res) => {
res.json({ sendsome: "data" })
});
app.listen(3001, () => {
console.log('server started on 3001');
});
Right now, I'm using a hack to send the cookie (creating a cookie in JS and sending it manually), but the hack is getting more and more tiresome as my project gets bigger. What do I need to add to this code to have the cookie send like when I was using just one app? Is express-session just not designed for this? (It seems like it would be, I know it is extremely common to have a separate app for both front end and back end.)
Should I expect the cookie to send automatically if the front end and back end have any sort of handshake? Do I need to mess with the cookie.domain
or cookie.sameSite
attributes in the express-session initialization object? Do I need to mess with CORS or the fetch()
Accept
header? Is res.json()
the wrong method to use? Is it easier to deal with a real IP and not localhost
? I've tried a bunch of things, but I no matter what I do, I can't get that blasted express-session generated session ID cookie on my browser.
It turns out that the problem was with the front end, not the back end, and it was a CORS issue. The express-session code was making the cookie just fine, but the front end couldn't accept it because having the back end hosted on port 3001 made it a cross-origin request (you'll recall the front end was on port 3000), even though both the front and back ends were on the same machine, localhost.
All I had to do was use the proxy
field in my package.json file in my React project, like so:
...
},
"proxy": "http://localhost:3001"
}
With the proxy change, I also had to change the fetch()
in my React code, from this:
fetch('http://localhost:3001/gimmecookie', {
...
to this:
fetch('/gimmecookie', {
...
as my proxy
field was tricking my React project into thinking my back end on a different port was actually on the same origin.
Side note: once I realized this was a front end CORS issue, I toyed around with some other solutions (such as using credentials: include
in the fetch()
init object), but it quickly became apparent that these solutions had significant drawbacks, until I found the proxy solution. Flippn' CORS!