Skip to content

Commit 73e0193

Browse files
Add sameSite 'auto' support to match secure 'auto' pattern (#1087)
* feat: add sameSite 'auto' support for automatic cross-site cookie configuration * test: enhance session tests for secure and SameSite cookie attributes * test: update session tests for SameSite cookie handling and logging * docs: add history entry * refactor: cache issecure result to avoid duplicate calls * Apply suggestion from @bjohansebas --------- Co-authored-by: Sebastian Beltran <[email protected]>
1 parent 264b6a0 commit 73e0193

File tree

5 files changed

+188
-2
lines changed

5 files changed

+188
-2
lines changed

HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
### 🚀 Improvements
44

5+
* Add sameSite 'auto' support for automatic SameSite attribute configuration
6+
7+
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.
8+
59
* deps: use tilde notation for dependencies
610

711
1.18.2 / 2025-07-17

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ By default, this is `false`.
130130
- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement.
131131
- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie.
132132
- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement.
133+
- `'auto'` will set the `SameSite` attribute to `None` for secure connections and `Lax` for non-secure connections.
133134

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

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

146155
Specifies the `boolean` value for the `Secure` `Set-Cookie` attribute. When truthy,

index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,14 @@ function session(options) {
160160
req.session = new Session(req);
161161
req.session.cookie = new Cookie(cookieOptions);
162162

163+
var isSecure = issecure(req, trustProxy);
164+
163165
if (cookieOptions.secure === 'auto') {
164-
req.session.cookie.secure = issecure(req, trustProxy);
166+
req.session.cookie.secure = isSecure;
167+
}
168+
169+
if (cookieOptions.sameSite === 'auto') {
170+
req.session.cookie.sameSite = isSecure ? 'none' : 'lax';
165171
}
166172
};
167173

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
},
4343
"scripts": {
4444
"lint": "eslint . && node ./scripts/lint-readme.js",
45-
"test": "./test/support/gencert.sh && mocha --require test/support/env --check-leaks --bail --no-exit --reporter spec test/",
45+
"test": "./test/support/gencert.sh && mocha --require test/support/env --check-leaks --no-exit --reporter spec test/",
4646
"test-ci": "nyc --reporter=lcov --reporter=text npm test",
4747
"test-cov": "nyc npm test",
4848
"version": "node scripts/version-history.js && git add HISTORY.md"

test/session.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,173 @@ describe('session()', function(){
801801
})
802802
})
803803
})
804+
805+
describe('when "sameSite" set to "auto"', function () {
806+
describe('basic functionality', function () {
807+
before(function () {
808+
function setup (req) {
809+
req.secure = JSON.parse(req.headers['x-secure'])
810+
}
811+
812+
function respond (req, res) {
813+
res.end(String(req.secure))
814+
}
815+
816+
this.server = createServer(setup, { cookie: { sameSite: 'auto' } }, respond)
817+
})
818+
819+
it('should set SameSite=None for secure connections', function (done) {
820+
request(this.server)
821+
.get('/')
822+
.set('X-Secure', 'true')
823+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
824+
.expect(200, 'true', done)
825+
})
826+
827+
it('should set SameSite=Lax for insecure connections', function (done) {
828+
request(this.server)
829+
.get('/')
830+
.set('X-Secure', 'false')
831+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
832+
.expect(200, 'false', done)
833+
})
834+
})
835+
836+
describe('with proxy settings', function () {
837+
describe('when "proxy" is "true"', function () {
838+
before(function () {
839+
this.server = createServer({ proxy: true, cookie: { sameSite: 'auto' }})
840+
})
841+
842+
it('should set SameSite=None when X-Forwarded-Proto is https', function (done) {
843+
request(this.server)
844+
.get('/')
845+
.set('X-Forwarded-Proto', 'https')
846+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
847+
.expect(200, done)
848+
})
849+
850+
it('should set SameSite=Lax when X-Forwarded-Proto is http', function (done) {
851+
request(this.server)
852+
.get('/')
853+
.set('X-Forwarded-Proto', 'http')
854+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
855+
.expect(200, done)
856+
})
857+
})
858+
859+
describe('when "proxy" is "false"', function () {
860+
before(function () {
861+
this.server = createServer({ proxy: false, cookie: { sameSite: 'auto' }})
862+
})
863+
864+
it('should set SameSite=Lax when X-Forwarded-Proto is https', function (done) {
865+
request(this.server)
866+
.get('/')
867+
.set('X-Forwarded-Proto', 'https')
868+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
869+
.expect(200, done)
870+
})
871+
})
872+
})
873+
874+
describe('combined with secure auto', function() {
875+
describe('when "secure" is "auto"', function () {
876+
before(function () {
877+
function setup (req) {
878+
req.secure = JSON.parse(req.headers['x-secure'])
879+
}
880+
881+
function respond (req, res) {
882+
res.end(String(req.secure))
883+
}
884+
885+
this.server = createServer(setup, { cookie: { secure: 'auto', sameSite: 'auto' } }, respond)
886+
})
887+
888+
it('should set both Secure and SameSite=None when secure', function (done) {
889+
request(this.server)
890+
.get('/')
891+
.set('X-Secure', 'true')
892+
.expect(shouldSetCookieWithAttribute('connect.sid', 'Secure'))
893+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
894+
.expect(200, 'true', done)
895+
})
896+
897+
it('should set neither Secure nor SameSite=None when insecure', function (done) {
898+
request(this.server)
899+
.get('/')
900+
.set('X-Secure', 'false')
901+
.expect(shouldSetCookieWithoutAttribute('connect.sid', 'Secure'))
902+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
903+
.expect(200, 'false', done)
904+
})
905+
})
906+
907+
describe('when "secure" is "false"', function () {
908+
before(function () {
909+
function setup (req) {
910+
req.secure = JSON.parse(req.headers['x-secure'])
911+
}
912+
913+
function respond (req, res) {
914+
res.end(String(req.secure))
915+
}
916+
917+
this.server = createServer(setup, { cookie: { secure: false, sameSite: 'auto' } }, respond)
918+
})
919+
920+
it('should set SameSite=None without Secure when secure', function (done) {
921+
request(this.server)
922+
.get('/')
923+
.set('X-Secure', 'true')
924+
.expect(shouldSetCookieWithoutAttribute('connect.sid', 'Secure'))
925+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
926+
.expect(200, 'true', done)
927+
})
928+
929+
it('should set SameSite=Lax without Secure when insecure', function (done) {
930+
request(this.server)
931+
.get('/')
932+
.set('X-Secure', 'false')
933+
.expect(shouldSetCookieWithoutAttribute('connect.sid', 'Secure'))
934+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'Lax'))
935+
.expect(200, 'false', done)
936+
})
937+
})
938+
939+
describe('when "secure" is "true"', function () {
940+
before(function () {
941+
function setup (req) {
942+
req.secure = JSON.parse(req.headers['x-secure'])
943+
}
944+
945+
function respond (req, res) {
946+
res.end(String(req.secure))
947+
}
948+
949+
this.server = createServer(setup, { cookie: { secure: true, sameSite: 'auto' } }, respond)
950+
})
951+
952+
it('should set both Secure and SameSite=None when secure', function (done) {
953+
request(this.server)
954+
.get('/')
955+
.set('X-Secure', 'true')
956+
.expect(shouldSetCookieWithAttribute('connect.sid', 'Secure'))
957+
.expect(shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None'))
958+
.expect(200, 'true', done)
959+
})
960+
961+
it('should not set cookie when insecure', function (done) {
962+
request(this.server)
963+
.get('/')
964+
.set('X-Secure', 'false')
965+
.expect(shouldNotHaveHeader('Set-Cookie'))
966+
.expect(200, 'false', done)
967+
})
968+
})
969+
})
970+
})
804971
})
805972

806973
describe('genid option', function(){

0 commit comments

Comments
 (0)