I've been working on a website that handles authentication with express-session. I found that the session is saved correctly in Chrome and Firefox, but not in Safari. In Safari, when I send a request to the server, the session variable is set, but after I redirect the client to another page, the variable is lost.
Here's a simple way to reproduce this issue:
// index.js
const app = require('express')();
const session = require('express-session');
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use(session({
secret: 'secret',
resave: false,
saveUninitialized: true,
cookie: {secure: false}
}));
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
app.get('/home', (req, res, next) => {
if (req.session && req.session.authenticated) {
next();
} else {
res.redirect('/');
}
}, (req, res) => {
res.sendFile(__dirname + '/home.html');
});
app.post('/login', (req, res) => {
if (req.body.password && req.body.password === '1234') {
req.session.authenticated = true;
res.status(200).json({success: true});
} else {
res.json({error: 'password incorrect'})
}
});
app.listen(8080);
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>express-session bug</title>
<script>
window.onload = () => {
const submit = document.querySelector('input[type="submit"]');
submit.onclick = (event) => {
event.preventDefault();
const password = document.querySelector('input[type="text"]').value;
fetch('/login', {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({password})
})
.then((data) => data.json())
.then((data) => {
if (data.success) {
window.location.href = '/home';
} else if (data.error){
console.log(data.error);
}
})
.catch(err => console.log(err));
};
};
</script>
</head>
<body>
<form>
<input type="text" name="password" />
<input type="submit" />
</form>
</body>
</html>
<!-- home.html -->
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>You are authenticated!</h1>
</body>
</html>
This works perfectly in Chrome but in Safari, when I type 1234, the browser must redirect me to /home, but when redirected, the authentication middleware on /home says that req.session.authenticated is not set. Is there anything I am missing, or is this a problem with express-session itself?
My Safari version is 11.1.2.
I was having this same issue as you but just figured it out. It's a problem with fetch - I guess with Safari you are required to pass in credentials: 'include', otherwise it doesn't send cookies (unlike in Chrome).
From what i have seen on the web, sessions are saved only when 2 (or maybe 3) methods are called.
res.redirect(), and res.render(), are supposed to save sessions if i'm correct.
When you just set res.status() or use any other way to send a result that isn't the two previous methods, the session isn't automatically saved. In this case, you have to do it manually with req.session.save();
Hope this helps you.
I'm trying to create a SPA with express and socketio, and i have the same kind of problems. Even when i call req.session.save manually, the session isn't actually saved. So maybe there is a deeper problem here ?
I guess there is no fix and we can not use sessions with express since we cannot use them on safari browsers? We have to move to JWT for session auth. I have been researching for days and cannot find fix for this issue and no one seems to have answers.
I don't have access to Safari personally, as it requires having an Apple computer. I'm not sure why it wouldn't work, but if someone can provide either a detailed break down of what makes Safari different or even a fix, I'm not sure how I can contribute in the resolution towards this.
freaking apple man, get with the times Mr. Tim Apple... :-)
This appears to have it done it for me...
xhrFields: {
withCredentials: true
}
Full implementation
function ajaxPost(url, data, cb) {
$.ajax({
url: url,
data: data,
type: 'POST',
cache: false,
redirect: 'follow',
xhrFields: {
withCredentials: true
}
}).done(function(ret) {
cb(ret)
})
}
Indeed, credentials: 'include fixed this one 馃拑
@bgrgndzz - this issue can be closed now, ya?
This doesn't work for me when the server is on a different domain.
This doesn't work for me when the server is on a different domain.
I don't know your setup, but recently the browser policy changed and they now mostly refuse to share ressources between different domains. This isn't related to expressjs.
A solution is to use subdomains instead.
Not sure if that is related to your issue. Anyway, i hope this helps.
Most helpful comment
I was having this same issue as you but just figured it out. It's a problem with fetch - I guess with Safari you are required to pass in
credentials: 'include', otherwise it doesn't send cookies (unlike in Chrome).