Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### 🚀 Improvements

* Add sameSite 'auto' support for automatic SameSite attribute configuration

Added `sameSite: 'auto'` option for cookie configuration that automatically sets `SameSite=None` for HTTPS and `SameSite=Lax` for HTTP connections, simplifying cookie handling across different environments.

* deps: use tilde notation for dependencies

1.18.2 / 2025-07-17
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ By default, this is `false`.
- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
- `'auto'` will set the `SameSite` attribute to `None` for secure connections and `Lax` for non-secure connections.

More information about the different enforcement levels can be found in
[the specification][rfc-6265bis-03-4.1.2.7].
Expand All @@ -141,6 +142,14 @@ the future. This also means many clients may ignore this attribute until they un
that requires that the `Secure` attribute be set to `true` when the `SameSite` attribute has been
set to `'none'`. Some web browsers or other clients may be adopting this specification.

The `cookie.sameSite` option can also be set to the special value `'auto'` to have
this setting automatically match the determined security of the connection. When the connection
is secure (HTTPS), the `SameSite` attribute will be set to `None` to enable cross-site usage.
When the connection is not secure (HTTP), the `SameSite` attribute will be set to `Lax` for
better security while maintaining functionality. This is useful when the Express `"trust proxy"`
setting is properly setup to simplify development vs production configuration, particularly
for SAML authentication scenarios.

##### cookie.secure

Specifies the `boolean` value for the `Secure` `Set-Cookie` attribute. When truthy,
Expand Down
8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,14 @@ function session(options) {
req.session = new Session(req);
req.session.cookie = new Cookie(cookieOptions);

const isSecure = issecure(req, trustProxy);

if (cookieOptions.secure === 'auto') {
req.session.cookie.secure = issecure(req, trustProxy);
req.session.cookie.secure = isSecure;
}

if (cookieOptions.sameSite === 'auto') {
req.session.cookie.sameSite = isSecure ? 'none' : 'lax';
}
};

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"scripts": {
"lint": "eslint . && node ./scripts/lint-readme.js",
"test": "./test/support/gencert.sh && mocha --require test/support/env --check-leaks --bail --no-exit --reporter spec test/",
"test": "./test/support/gencert.sh && mocha --require test/support/env --check-leaks --no-exit --reporter spec test/",
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
"test-cov": "nyc npm test",
"version": "node scripts/version-history.js && git add HISTORY.md"
Expand Down
167 changes: 167 additions & 0 deletions test/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,173 @@ describe('session()', function(){
})
})
})

describe('when "sameSite" set to "auto"', function () {
describe('basic functionality', function () {
before(function () {
function setup (req) {
req.secure = JSON.parse(req.headers['x-secure'])
}

function respond (req, res) {
res.end(String(req.secure))
}

this.server = createServer(setup, { cookie: { sameSite: 'auto' } }, respond)
})

it('should set SameSite=None for secure connections', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'true')
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
.expect(200, 'true', done)
})

it('should set SameSite=Lax for insecure connections', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'false')
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
.expect(200, 'false', done)
})
})

describe('with proxy settings', function () {
describe('when "proxy" is "true"', function () {
before(function () {
this.server = createServer({ proxy: true, cookie: { sameSite: 'auto' }})
})

it('should set SameSite=None when X-Forwarded-Proto is https', function (done) {
request(this.server)
.get('/')
.set('X-Forwarded-Proto', 'https')
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
.expect(200, done)
})

it('should set SameSite=Lax when X-Forwarded-Proto is http', function (done) {
request(this.server)
.get('/')
.set('X-Forwarded-Proto', 'http')
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
.expect(200, done)
})
})

describe('when "proxy" is "false"', function () {
before(function () {
this.server = createServer({ proxy: false, cookie: { sameSite: 'auto' }})
})

it('should set SameSite=Lax when X-Forwarded-Proto is https', function (done) {
request(this.server)
.get('/')
.set('X-Forwarded-Proto', 'https')
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
.expect(200, done)
})
})
})

describe('combined with secure auto', function() {
describe('when "secure" is "auto"', function () {
before(function () {
function setup (req) {
req.secure = JSON.parse(req.headers['x-secure'])
}

function respond (req, res) {
res.end(String(req.secure))
}

this.server = createServer(setup, { cookie: { secure: 'auto', sameSite: 'auto' } }, respond)
})

it('should set both Secure and SameSite=None when secure', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'true')
.expect(shouldSetCookieWithAttribute('connect.sid', 'Secure'))
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
.expect(200, 'true', done)
})

it('should set neither Secure nor SameSite=None when insecure', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'false')
.expect(shouldSetCookieWithoutAttribute('connect.sid', 'Secure'))
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
.expect(200, 'false', done)
})
})

describe('when "secure" is "false"', function () {
before(function () {
function setup (req) {
req.secure = JSON.parse(req.headers['x-secure'])
}

function respond (req, res) {
res.end(String(req.secure))
}

this.server = createServer(setup, { cookie: { secure: false, sameSite: 'auto' } }, respond)
})

it('should set SameSite=None without Secure when secure', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'true')
.expect(shouldSetCookieWithoutAttribute('connect.sid', 'Secure'))
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
.expect(200, 'true', done)
})

it('should set SameSite=Lax without Secure when insecure', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'false')
.expect(shouldSetCookieWithoutAttribute('connect.sid', 'Secure'))
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
.expect(200, 'false', done)
})
})

describe('when "secure" is "true"', function () {
before(function () {
function setup (req) {
req.secure = JSON.parse(req.headers['x-secure'])
}

function respond (req, res) {
res.end(String(req.secure))
}

this.server = createServer(setup, { cookie: { secure: true, sameSite: 'auto' } }, respond)
})

it('should set both Secure and SameSite=None when secure', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'true')
.expect(shouldSetCookieWithAttribute('connect.sid', 'Secure'))
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
.expect(200, 'true', done)
})

it('should not set cookie when insecure', function (done) {
request(this.server)
.get('/')
.set('X-Secure', 'false')
.expect(shouldNotHaveHeader('Set-Cookie'))
.expect(200, 'false', done)
})
Comment on lines +961 to +967
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior here is interesting and cannot be changed since it would be a breaking change. However, because secure is set to true in the configuration, during cookie creation, which happens on this line

req.session.cookie = new Cookie(cookieOptions);
takes the value from the configuration rather than from the route. As a result, the cookie ends up being a secure cookie. Since it is a secure cookie, it will not be created when the request is insecure (HTTP)
if (req.session.cookie.secure && !issecure(req, trustProxy)) {
.

})
})
})
})

describe('genid option', function(){
Expand Down