Skip to content

Commit a34fd39

Browse files
fracmakdougwilson
authored andcommitted
Support the value "auto" in the "cookie.secure" option
closes expressjs#209
1 parent 7824746 commit a34fd39

4 files changed

Lines changed: 94 additions & 3 deletions

File tree

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
unreleased
22
==========
33

4+
* Support the value `'auto'` in the `cookie.secure` option
45
* deps: cookie@0.2.2
56
- Throw on invalid values provided to `serialize`
67
* deps: depd@~1.1.0

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ if (app.get('env') === 'production') {
192192
app.use(session(sess))
193193
```
194194

195+
The `cookie.secure` option can also be set to the special value `'auto'` to have
196+
this setting automatically match the determined security of the connection. Be
197+
careful when using this setting if the site is available both as HTTP and HTTPS,
198+
as once the cookie is set on HTTPS, it will no longer be visible over HTTP. This
199+
is useful when the Express `"trust proxy"` setting is properly setup to simplify
200+
development vs production configuration.
201+
195202
By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set
196203
so the cookie becomes a browser-session cookie. When the user closes the
197204
browser the cookie (and session) will be removed.

index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ function session(options){
8686
// name - previously "options.key"
8787
, name = options.name || options.key || 'connect.sid'
8888
, store = options.store || new MemoryStore
89-
, cookie = options.cookie || {}
9089
, trustProxy = options.proxy
9190
, storeReady = true
9291
, rollingSessions = options.rolling || false;
92+
var cookieOptions = options.cookie || {};
9393
var resaveSession = options.resave;
9494
var saveUninitializedSession = options.saveUninitialized;
9595
var secret = options.secret;
@@ -139,7 +139,11 @@ function session(options){
139139
store.generate = function(req){
140140
req.sessionID = generateId(req);
141141
req.session = new Session(req);
142-
req.session.cookie = new Cookie(cookie);
142+
req.session.cookie = new Cookie(cookieOptions);
143+
144+
if (cookieOptions.secure === 'auto') {
145+
req.session.cookie.secure = issecure(req, trustProxy);
146+
}
143147
};
144148

145149
var storeImplementsTouch = typeof store.touch === 'function';
@@ -156,7 +160,7 @@ function session(options){
156160

157161
// pathname mismatch
158162
var originalPath = parseUrl.original(req).pathname;
159-
if (0 != originalPath.indexOf(cookie.path || '/')) return next();
163+
if (originalPath.indexOf(cookieOptions.path || '/') !== 0) return next();
160164

161165
// ensure a secret is available or bail
162166
if (!secret && !req.secret) {

test/session.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,67 @@ describe('session()', function(){
638638
})
639639
})
640640

641+
describe('cookie option', function () {
642+
describe('when "secure" set to "auto"', function () {
643+
describe('when "proxy" is "true"', function () {
644+
before(function () {
645+
this.server = createServer({ proxy: true, cookie: { maxAge: 5, secure: 'auto' }})
646+
})
647+
648+
it('should set secure when X-Forwarded-Proto is https', function (done) {
649+
request(this.server)
650+
.get('/')
651+
.set('X-Forwarded-Proto', 'https')
652+
.expect(shouldSetCookie('connect.sid'))
653+
.expect(shouldSetSecureCookie('connect.sid'))
654+
.expect(200, done)
655+
})
656+
})
657+
658+
describe('when "proxy" is "false"', function () {
659+
before(function () {
660+
this.server = createServer({ proxy: false, cookie: { maxAge: 5, secure: 'auto' }})
661+
})
662+
663+
it('should not set secure when X-Forwarded-Proto is https', function (done) {
664+
request(this.server)
665+
.get('/')
666+
.set('X-Forwarded-Proto', 'https')
667+
.expect(shouldSetCookie('connect.sid'))
668+
.expect(shouldNotSetSecureCookie('connect.sid'))
669+
.expect(200, done)
670+
})
671+
})
672+
673+
describe('when "proxy" is undefined', function() {
674+
before(function () {
675+
this.app = express()
676+
.use(function(req, res, next) { Object.defineProperty(req, 'secure', { value: JSON.parse(req.headers['x-secure']) }); next(); })
677+
.use(session({ secret: 'keyboard cat', cookie: { maxAge: min, secure: 'auto' }}))
678+
.use(function(req, res) { res.json(req.secure); });
679+
})
680+
681+
it('should set secure if req.secure = true', function (done) {
682+
request(this.app)
683+
.get('/')
684+
.set('X-Secure', 'true')
685+
.expect(shouldSetCookie('connect.sid'))
686+
.expect(shouldSetSecureCookie('connect.sid'))
687+
.expect(200, 'true', done)
688+
})
689+
690+
it('should not set secure if req.secure = false', function (done) {
691+
request(this.app)
692+
.get('/')
693+
.set('X-Secure', 'false')
694+
.expect(shouldSetCookie('connect.sid'))
695+
.expect(shouldNotSetSecureCookie('connect.sid'))
696+
.expect(200, 'false', done)
697+
})
698+
})
699+
})
700+
})
701+
641702
describe('genid option', function(){
642703
it('should reject non-function values', function(){
643704
assert.throws(session.bind(null, { genid: 'bogus!' }), /genid.*must/)
@@ -2055,6 +2116,15 @@ function shouldNotHaveHeader(header) {
20552116
}
20562117
}
20572118

2119+
function shouldNotSetSecureCookie(name) {
2120+
return function (res) {
2121+
var header = cookie(res)
2122+
assert.ok(header, 'should have a cookie header')
2123+
assert.equal(header.split('=')[0], name, 'should set cookie ' + name)
2124+
assert.ok(header.toLowerCase().split(/; */).every(function (k) { return k !== 'secure'; }), 'should not set secure cookie')
2125+
}
2126+
}
2127+
20582128
function shouldSetCookie(name) {
20592129
return function (res) {
20602130
var header = cookie(res)
@@ -2072,6 +2142,15 @@ function shouldSetCookieToValue(name, val) {
20722142
}
20732143
}
20742144

2145+
function shouldSetSecureCookie(name) {
2146+
return function (res) {
2147+
var header = cookie(res)
2148+
assert.ok(header, 'should have a cookie header')
2149+
assert.equal(header.split('=')[0], name, 'should set cookie ' + name)
2150+
assert.ok(header.toLowerCase().split(/; */).some(function (k) { return k === 'secure'; }), 'should set secure cookie')
2151+
}
2152+
}
2153+
20752154
function sid(res) {
20762155
var match = /^[^=]+=s%3A([^;\.]+)[\.;]/.exec(cookie(res))
20772156
var val = match ? match[1] : undefined

0 commit comments

Comments
 (0)