Reputation: 2965
I can't get fetch to send a cookie. I read that for cross origin request, you must use credentials: 'include'
. But this still isn't giving me cookies.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>fetch on load</div>
<script>
fetch('http://localhost:4001/sayhi', { credentials: 'include' })
.then((res) => {
return res.text();
})
.then((text) => {
console.log('fetched: ' + text);
})
.catch(console.log);
</script>
</body>
</html>
const express = require('express');
const mongoose = require('mongoose');
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
const app = express();
const cors = require('cors');
app.use(cors());
app.use((req, res, next) => {
console.log(req.path);
// this will log a cookie when several
// requests are sent through the browser
// but 'undefined' through `fetch`
console.log('cookie in header: ', req.headers.cookie);
next();
});
app.use(
session({
secret: 'very secret 12345',
resave: true,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);
app.use(async (req, res, next) => {
console.log(`${req.method}: ${req.path}`);
try {
req.session.visits = req.session.visits ? req.session.visits + 1 : 1;
return next();
} catch (err) {
return next(err);
}
});
app.get('/sayhi', (req, res, next) => {
res.send('hey');
});
(async () =>
mongoose.connect('mongodb://localhost/oneSession', {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: true,
}))()
.then(() => {
console.log(`Connected to MongoDB set user test`);
app.listen(4001).on('listening', () => {
console.log('info', `HTTP server listening on port 4001`);
});
})
.catch((err) => {
console.error(err);
});
The middleware that runs console.log('cookie in header: ', req.headers.cookie);
returns undefined for every fetch request.
Every fetch request is also creating a new session, I believe because the cookie isn't being set.
How can I get cookies to send with fetch?
I think adding these helped so far:
//instead of app.use(cors());
app.use(
cors({
credentials: true,
origin: 'http://localhost:5501', // your_frontend_domain, it's an example
})
);
app.use((req, res, next) => {
`res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5501');` // your_frontend_domain, it's an example
next()
});
But I still have a problem:
In devtools, I went to 'Network' and refreshed the html file to send the request again. There I saw the response headers. I could see that the Set-Cookie header was sent but had a yellow triangle warning.
It says to set sameSite
to true
.
I needed to add cookie: { sameSite: 'none' }
to the session options
app.use(
session({
secret: 'very secret 12345',
resave: true,
cookie: { sameSite: 'none' },
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
})
);
After I did that I got a new yellow triangle warning by the cookie that said I needed to set another option on cookie- secure: true
. But this means only requests that send over HTTPS will work. But I'm in development and can't send over https.
Upvotes: 6
Views: 9441
Reputation: 2965
The solution below worked when I was navigating on the browser to http://127.0.0.1:5501/index.html'
. But if you navigate to localhost
instead of 127.0.0.1
Jack Yu's answer works.
const express = require('express');
const session = require('express-session');
const app = express();
const cors = require('cors');
app.use(
cors({
credentials: true,
origin: 'http://localhost:5501',
})
);
app.use(
session({
saveUninitialized: false,
resave: false,
secret: 'very secret 12345',
cookie: {
secure: false,
sameSite: 'none',
},
})
);
app.use(async (req, res, next) => {
res.header('Access-Control-Allow-Credentials', true);
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, authorization'
);
res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE,PUT,OPTIONS');
res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5501');
try {
req.session.visits = req.session.visits ? req.session.visits + 1 : 1;
console.log('req.session.visits: ', req.session.visits);
return next();
} catch (err) {
return next(err);
}
});
app.get('/sayhi', (req, res, next) => {
res.send('hey');
});
app.listen(4001).on('listening', () => {
console.log('info', `HTTP server listening on port 4001`);
});
This works on firefox. But it still didn't work on chrome because Chrome now only delivers cookies with cross-site requests if they are set with SameSite=None
and Secure
. Mine is set to secure: false
because I am not sending over HTTPS for development. So I followed these instructions:
You can completely disable this feature by going to "chrome://flags" and disabling "Cookies without SameSite must be secure". However, this will disable it for all sites, so it will be less secure when you aren't developing too.
But it concerns me that there isn't a better solution. I don't want to disable "Cookies without SameSite must be secure"
for all sites if there's a security reason I shouldn't.
Upvotes: 2
Reputation: 2425
I wrote an example for your.
You don't need to setup "none" for sameSite attribute.
{
saveUninitialized: false,
resave: false,
secret: 'very secret 12345',
cookie: {
secure: false,
}
}
// server.js
const express = require('express');
const app = express();
app.use(express.static("./public"));
app.listen(5001);
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
fetch('http://localhost:4001/test', {
method: "POST",
credentials: "include"
}).then(response => {
return response.json()
}).then(json => {
alert('Received: ' + JSON.stringify(json));
})
</script>
</body>
</html>
// other-server.js
const express = require('express');
const app = express();
const session = require('express-session')
var sess = {
saveUninitialized: false,
resave: false,
secret: 'very secret 12345',
cookie: {
secure: false,
}
}
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(session(sess))
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Accept, Content-Type")
res.setHeader("Access-Control-Allow-Origin", "http://localhost:5001")
res.setHeader("Access-Control-Allow-Credentials", true)
res.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH")
next();
})
app.post('/test', (req, res) => {
console.log(req.sessionID);
req.session.a = "hi"
res.json({a: 1})
})
app.listen(4001, () => {
console.log('start');
});
Upvotes: 1