Session: Safari doesn't save session after an ajax request

Created on 2 Sep 2018  路  9Comments  路  Source: expressjs/session

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.

investigate

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).

All 9 comments

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fibo picture fibo  路  22Comments

aheyer picture aheyer  路  15Comments

scaryguy picture scaryguy  路  16Comments

renehauck picture renehauck  路  16Comments

antishok picture antishok  路  27Comments