I've been studying this bug for a while. I have a NodeJS application with cookies when logging in, and the cookies are supposed to last one year. I check the application every day on Google Chrome, where it works fine, and on Safari, where I have to login again every week or so. Both browsers keep the cookies and I can check the database for them.
The Google Chrome cookie (obfuscated on purpose) is:
> db.sessions.find({_id: "Sfztzfn6jhucgI3r6o9p3bw2wt"})
[
{
_id: 'Sfztzfn6jhucgI3r6o9p3bw2wt',
expires: ISODate('2026-02-08T08:09:10.166Z'),
session: {
cookie: {
originalMaxAge: 31535999999,
partitioned: null,
priority: null,
expires: ISODate('2026-02-08T08:09:10.166Z'),
secure: null,
httpOnly: true,
domain: null,
path: '/',
sameSite: 'lax'
},
passport: { user: 'Cw2mwjwn2my9sOhtldwgah4e4a' },
flash: {}
}
}
]
The Safari cookie in the database is similar, but it is missing the passport
field, and I believe this is why I need to login again on Safari:
> db.sessions.find({_id: "3NiGSxZSvoF354waptnhswaK7wmgvdqnbgzw"})
[
{
_id: '3NiGSxZSvoF354waptnhswaK7wmgvdqnbgzw',
expires: ISODate('2026-02-15T07:34:28.833Z'),
session: {
cookie: {
originalMaxAge: 31536000000,
partitioned: null,
priority: null,
expires: ISODate('2026-02-15T07:34:28.833Z'),
secure: null,
httpOnly: true,
domain: null,
path: '/',
sameSite: 'lax'
},
flash: {}
}
}
]
Here is the cookie on the Safari inspection blade, which has the same expiration date:
The NodeJS code to save cookies is:
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const max_session_ms = 365 * 24 * 60 * 60 * 1000;
// Initialize mongodb session storage to remember users.
const store = new MongoDBStore({
uri: MONGO_URI,
expires: max_session_ms,
});
// Enable sessions using encrypted cookies
app.use(
session({
cookie: {
maxAge: max_session_ms,
sameSite: "lax",
},
store: store,
secret: "some secrete",
signed: true,
resave: false, // Unknown effect. See
saveUninitialized: false, // Save only explicitly, e.g. when logging in.
httpOnly: true, // Don't let browser javascript access cookies.
secure: true, // Use cookies over https in production.
})
);
If I login on Safari again, then I can access the web app for around 7 days, until the session in the database loses the passport
field again. This only happens for Safari, not for Google Chrome, and has been happening for several months.
How can I investigate and fix this bug?
I've been studying this bug for a while. I have a NodeJS application with cookies when logging in, and the cookies are supposed to last one year. I check the application every day on Google Chrome, where it works fine, and on Safari, where I have to login again every week or so. Both browsers keep the cookies and I can check the database for them.
The Google Chrome cookie (obfuscated on purpose) is:
> db.sessions.find({_id: "Sfztzfn6jhucgI3r6o9p3bw2wt"})
[
{
_id: 'Sfztzfn6jhucgI3r6o9p3bw2wt',
expires: ISODate('2026-02-08T08:09:10.166Z'),
session: {
cookie: {
originalMaxAge: 31535999999,
partitioned: null,
priority: null,
expires: ISODate('2026-02-08T08:09:10.166Z'),
secure: null,
httpOnly: true,
domain: null,
path: '/',
sameSite: 'lax'
},
passport: { user: 'Cw2mwjwn2my9sOhtldwgah4e4a' },
flash: {}
}
}
]
The Safari cookie in the database is similar, but it is missing the passport
field, and I believe this is why I need to login again on Safari:
> db.sessions.find({_id: "3NiGSxZSvoF354waptnhswaK7wmgvdqnbgzw"})
[
{
_id: '3NiGSxZSvoF354waptnhswaK7wmgvdqnbgzw',
expires: ISODate('2026-02-15T07:34:28.833Z'),
session: {
cookie: {
originalMaxAge: 31536000000,
partitioned: null,
priority: null,
expires: ISODate('2026-02-15T07:34:28.833Z'),
secure: null,
httpOnly: true,
domain: null,
path: '/',
sameSite: 'lax'
},
flash: {}
}
}
]
Here is the cookie on the Safari inspection blade, which has the same expiration date:
The NodeJS code to save cookies is:
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const max_session_ms = 365 * 24 * 60 * 60 * 1000;
// Initialize mongodb session storage to remember users.
const store = new MongoDBStore({
uri: MONGO_URI,
expires: max_session_ms,
});
// Enable sessions using encrypted cookies
app.use(
session({
cookie: {
maxAge: max_session_ms,
sameSite: "lax",
},
store: store,
secret: "some secrete",
signed: true,
resave: false, // Unknown effect. See https://github/expressjs/session#resave
saveUninitialized: false, // Save only explicitly, e.g. when logging in.
httpOnly: true, // Don't let browser javascript access cookies.
secure: true, // Use cookies over https in production.
})
);
If I login on Safari again, then I can access the web app for around 7 days, until the session in the database loses the passport
field again. This only happens for Safari, not for Google Chrome, and has been happening for several months.
How can I investigate and fix this bug?
Share Improve this question asked Feb 16 at 11:16 emonigmaemonigma 4,4164 gold badges37 silver badges83 bronze badges 2 |1 Answer
Reset to default 0Apple's Intelligent Tracking Prevention (ITP) used in Safari's browser engine Webkit, limits 3rd party cookies to 7 days by default and 24 hours if the URL has query parameters.
Given your domain: null
option within the cookie, Safari probably sees that as a 3rd party cookie and is therefore limiting it to the 7 days.
Setting domain: mysite
where mysite
is the domain you intend for the cookie to be used, should tell Safari that it's a 1st party cookie.
Note 1: the cookie.domian
property in express-session
:
By default, no domain is set, and most clients will consider the cookie to apply to only the current domain.
So you could also try omitting the domain
property altogether. However, the "most clients" is where Safari might not fit.
Note 2: Webkits 7-Day Cap on All Script-Writeable Storage rules mean that:
ITP deletes all cookies created in JavaScript and all other script-writeable storage after 7 days of no user interaction with the website. The latter storage forms are:
- List item
- IndexedDB
- LocalStorage
- Media keys
- SessionStorage
- Service Worker registrations and cache
domain: null
setting is actually what you use then you should try setting the domain to your website domain instead so that it's considered a first-party one. There is a good blog on this subject you might find useful stape.io/blog/… – jQueeny Commented Feb 16 at 17:51