Session: using secure=auto with sameSite=none

Created on 3 Feb 2020  路  20Comments  路  Source: expressjs/session

With the upcoming chrome 80 release and the need to set sameSite=none for cross-browser requests it would be good to consider the scenario where secure is set to 'auto' (set based on 'x-forwarded-proto' header).
Chrome will start rejecting non 'secure' SameSite=None cookies. Therefore if the 'secure' attribute is not being set based on incoming request then sameSite=None option should revert to 'Lax'.

discuss

Most helpful comment

For anyone looking for a quick workaround: if you need (that is: SameSite=none in secure contexts and SameSite=lax in non-secure context for Chrome >= 81) here's one option:

Define sameSite property on Cookie.prototype

Object.defineProperty(expressSession.Cookie.prototype, 'sameSite', {
  // sameSite cannot be set to `None` if cookie is not marked secure
  get() {
    return this._sameSite === 'none' && !this.secure ? 'lax' : this._sameSite;
  },
  set(value) {
    this._sameSite = value;
  }
});

And set sameSite to none in cookieOptions:

app.use(expressSession({
 // ... other options
  cookie: {
    secure: 'auto',
    sameSite: 'none'
  }
}));

All 20 comments

Hm. I wonder if (1) is this only Chrome that does this, or it is all web browser (perhaps in a RFC spec somewhere) and (2) I don't think of course that if the user put sameSite: 'none' it should suddenly become lax; there would need to probably be some other kind of value to use instead to indicate you just want like "as high as possible" or something.

Taking a look to remind myself what SameSite=none does, I think that it's not possible to do what you're asking. This is because SameSite=none means you need you cookie available cross-site, but the other values (like Lax) do not allow that. So it would seem that if this module just changed the value, your site's functionality would fundamentally be broken, no?

What would be the use-case where you would want it to be SameSite=none when secure but something different when unsecure? How would the cross-site behavior work in the unsecure case?

@dougwilson
I think chrome is the first to enforce it and other browsers will follow. Check this link for details:
https://www.chromium.org/updates/same-site
https://www.chromestatus.com/feature/5633521622188032

The scenario where this is required:
A production site that allows itself to be embedded as an iframe at any origin but still uses cookies for authentication (sameSite=None required in Chrome 80). The production server(s) are behind an SSL proxy and we use proxy=true and secure='auto' options so that setting 'secure' is determined per-request by the 'x-forwarded-proto' header if it exists. This allows public access via the proxy (sameSite=None; secure) with full functionality. And it allows indirect access behind the SSL proxy (think internal status pages, etc.) with (sameSite=Lax) but these pages cannot be embedded in an iframe on a different origin.

suggest a validation check to ensure if 'secure' is not set then do not allow 'sameSite=None' as this combo will be rejected in chrome 80+. Instead of triggering an exception, just fallback to 'sameSite=Lax' ?

Gotcha. I would be hesitant to actually change this until there are other browsers actually rejecting the cookie as well, otherwise there isn't anything particularly wrong with the combination of samesite=none without secure to everything except just a single web browser... it's hard to know at this point if this is going to be something that will stay set in stone or not. I think to actually do something like this, it either needs a standard out there or at least two web browsers need to be implementing it (or maybe more?).

suggest a validation check to ensure if 'secure' is not set then do not allow 'sameSite=None' as this combo will be rejected in chrome 80+.

The issue is that (1) this is not backwards compatible, as it will suddenly make working configurations start throwing but also (2) as far as I can tell this combo is valid on all web browsers / client except one (unreleased) one.

Instead of triggering an exception, just fallback to 'sameSite=Lax' ?

That could be done, though does also seem not to be backwards-compatible. In your scenario, above, though, doesn't samesite=strict work? Why choose lax over strict? Maybe we need more users to chime in over time here with their scenarios to make sure we are implementing something that works for everyone.

It looks like the spec is still in 'working draft' state, but since Chrome has 64% browser market share it's definitely something to consider.
The fallback does not specifically have to be Lax (it just can not be set to None). A sensible default (if following googles lead) would be Lax as that is Chrome's new default when the option is not specified. Note: the fallback is only needed when using 'secure=auto'.

It looks like the spec is still in 'working draft' state, but since Chrome has 64% browser market share it's definitely something to consider.

Definately, but of course there are also a lot of thing that are not web browsers that access websites, which doesn't seem to be included in your list. This module is not specific to only web browsers, for example, so ideally should take into account everything it works under.

The fallback does not specifically have to be Lax (it just can not be set to None). A sensible default (if following googles lead) would be Lax as that is Chrome's new default when the option is not specified. Note: the fallback is only needed when using 'secure=auto'.

Right, that's why I marked this as "discussion" as I don't think there is a clear answer at this point. Ideally before making the change we can work out what the _right_ change to make is. If that turns out to be backwards-compatible we can land in the 1.x line and release a minor or patch. Of course if we need to change it later, that likely becomes a major at that point, which is why it is important to get a clear decision before we make and release it.

As it stands right now, the current settings in this module don't violate any specs, and there is just the _protentional_ for it not to work on a single web browser in the future, maybe. Reading the link you gave, it sounds like Google isn't even confident about the change and it doing staged rollouts and trials still, so there is still a lot to see here on what is even going to happen in regards to this.

For anyone looking for a quick workaround: if you need (that is: SameSite=none in secure contexts and SameSite=lax in non-secure context for Chrome >= 81) here's one option:

Define sameSite property on Cookie.prototype

Object.defineProperty(expressSession.Cookie.prototype, 'sameSite', {
  // sameSite cannot be set to `None` if cookie is not marked secure
  get() {
    return this._sameSite === 'none' && !this.secure ? 'lax' : this._sameSite;
  },
  set(value) {
    this._sameSite = value;
  }
});

And set sameSite to none in cookieOptions:

app.use(expressSession({
 // ... other options
  cookie: {
    secure: 'auto',
    sameSite: 'none'
  }
}));

Looks like Google is rolling back Chrome's _treat cookies as Lax by default_ enforcement.
Ongoing status: https://www.chromium.org/updates/same-site

yes the change is being rolled back as they believe the timing is not good. We are taking a greater effort with the session module and in the near future will be working vigorously through these discussions.

This issue is not generating any further discussion, and it seems to be resolved by updates with google's chrome intentions. Closing. Can re-open if needed.

Not able to set SameSite none secure in the express session.

Whenever I am setting SameSite : 'None', secure: true the code is not sending cookie at all.

var sess = {
    secret: 'Somekey',
    cookie: { httpOnly: false, SameSite : 'None', secure: true  },
    store: new MongoStore({
        url: "mongodb://" + mongoUser + ":" + mongoUserPass + "@" + mongoHost + "/" + mongoDB + "?authSource=admin&connectTimeoutMS=300000",
        ttl: 15 * 60
    }),
    saveUninitialized: true,
    resave: true,
    maxAge: 900000 //15 Minutes
}
app.use(session(sess))

Please help me.

For anyone looking for a quick workaround: if you need (that is: SameSite=none in secure contexts and SameSite=lax in non-secure context for Chrome >= 81) here's one option:

Define sameSite property on Cookie.prototype

Object.defineProperty(expressSession.Cookie.prototype, 'sameSite', {
  // sameSite cannot be set to `None` if cookie is not marked secure
  get() {
    return this._sameSite === 'none' && !this.secure ? 'lax' : this._sameSite;
  },
  set(value) {
    this._sameSite = value;
  }
});

And set sameSite to none in cookieOptions:

app.use(expressSession({
 // ... other options
  cookie: {
    secure: 'auto',
    sameSite: 'none'
  }
}));

The workaround suggested by @pirxpilot works great. In our case, we wanted non cross-domain requests to work in HTTP or HTTPS, and cross-domain requests to work in HTTPS. So SameSite=none had to be gone for HTTP to work when non cross-domain.

What would make sense to me is that sameSite could be set to 'auto', and the logic would follow the same as for secure, i.e. 'lax' when secure==false, and 'none' when secure==true.

What would make sense to me is that sameSite could be set to 'auto', and the logic would follow the same as for secure, i.e. 'lax' when secure==false, and 'none' when secure==true.

Yea, this has been proposed before, but this unfortunately doesn't make sense. If your site works with lax already, then just set it to lax... if you actually need the none value, it suddenly changing to lax is going to break behavior with your site. The secure flag doesn't change the visible behavior of the app like the sameSite flag does.

Then I don't know how to proceed but with a patch like the one @pirxpilot suggested, as I need sameSite to change value according to if the connection is secure or not.

The question would be, though, why would your site work in lax but yet you need none as well? And also why the choice of none and lax and not, for example, strict and none, or strict and lax? Auto works for secure also in that way because there are inky two choices, but there are three (four in chrome) choices for sameSite.

I can't go in details (NDA), but we have a web application served by server A that uses a REST API on that same server, and a web application from another server B that needs to use that same API of server A (in cross domain). Because of the costs to make everything work in HTTPS, we would rather have just the communication between B and A to be HTTPS.

I understand your point of strict vs lax, strict vs none, lax vs none, etc. Then maybe a function could be supplied to cookie.sameSite setting, so one could provide the logic they want if they need that value to be dynamic. I don't think there would be a need for any parameter to that function but the secure value telling if the cookie is secure or not.

Else, separate sameSite into two configuration settings: secureSameSite and unsecureSameSite so we can provide what SameSite value should be used when the cookie is secure or not.

I am aware these are not elegant solutions, I am brainstorming how the cookie settings could be modified to let sameSite become sort of dynamic depending if cookie is secure or not.

Until then (if it ever happens), we will be using the patch, that's all.

The main issue is that one could end up they need a different value for each and every config option in different situations. The good thing is that Express already provides a generic solution for this: construct the middleware for each config set you need and switch between them based on your parameters:

app.use(when((req) => req.protocol === 'https', session({/* https config */}), session({/* http config */})))

function when (test, a, b) {
  return (req, res, next) => (test(req, res) ? a : b)(req, res, next)
}

@dougwilson I agree that what you propose looks like a very robust approach. If it works it's definitely better than patching the session.Cookie prototype. Thanks for this.

The main issue is that one could end up they need a different value for each and every config option in different situations. The good thing is that Express already provides a generic solution for this: construct the middleware for each config set you need and switch between them based on your parameters:

app.use(when((req) => req.protocol === 'https', session({/* https config */}), session({/* http config */})))

function when (test, a, b) {
  return (req, res, next) => (test(req, res) ? a : b)(req, res, next)
}

Can you explain your approach a little bit more? (specially if there is not a patching planned for this issue). For this session configuration:

module.exports = session({
  cookie: {
    secure: false,
    httpOnly: true,
    sameSite: 'none',
    maxAge: 60 * 60 * 24 * 1000
  }
});

I am trying to connect this domain: https://josuevalrob.github.io/S2R2-PROJECT/ with this backend:
http://josuevalrob.webfactional.com/ (is not secure)
How should I implement it?

app.use(when((req) => req.protocol === 'https', session({
cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'none',
    maxAge: 60 * 60 * 24 * 1000
  }}), session({
cookie: {
    secure: false,
    httpOnly: false,
    sameSite: 'none',
    maxAge: 60 * 60 * 24 * 1000
  }})))

function when (test, a, b) {
  return (req, res, next) => (test(req, res) ? a : b)(req, res, next)
}

Is it correct??

Was this page helpful?
0 / 5 - 0 ratings