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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Session data is _not_ saved in the cookie itself, just the session ID.
#### Options

- `name` - cookie name (formerly known as `key`). (default: `'connect.sid'`)
- `headerName` - optional HTTP header name to pass session ID, e.g. `X-Session-Token`. (default: `undefined`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Discuss: change to just header, or probably better, id-header?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@Fishrock123 I think header will be better name for this option.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

in index.js
var signed = 's:' + signature.sign(val, secret);

secret is an Array, instead of a string, so your code has broken.

- `store` - session store instance.
- `secret` - session cookie is signed with this secret to prevent tampering.
- `cookie` - session cookie settings.
Expand Down
82 changes: 66 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,21 @@ function session(options){
var options = options || {}
// name - previously "options.key"
, name = options.name || options.key || 'connect.sid'
// optional name of HTTP header to pass session ID, e.g. 'X-Session-Token'
, headerName = options.header
, store = options.store || new MemoryStore
, cookie = options.cookie || {}
, trustProxy = options.proxy
, storeReady = true
, rollingSessions = options.rolling || false;
var resaveSession = options.resave;
var saveUninitializedSession = options.saveUninitialized;
var isCookieConfigurationSet = options.cookie !== null;
var headerNameNormalized;
if (headerName) {
// lower-case representation of header name to fetch header value from req.headers
headerNameNormalized = headerName.toLowerCase();
}

var generateId = options.genid || generateSessionId;

Expand Down Expand Up @@ -126,7 +134,9 @@ function session(options){
store.generate = function(req){
req.sessionID = generateId(req);
req.session = new Session(req);
req.session.cookie = new Cookie(cookie);
if (isCookieConfigurationSet) {
req.session.cookie = new Cookie(cookie);
}
};

var storeImplementsTouch = typeof store.touch === 'function';
Expand All @@ -145,9 +155,11 @@ function session(options){
// the store has temporarily disconnected etc
if (!storeReady) return debug('store is disconnected'), next();

// pathname mismatch
var originalPath = parseUrl.original(req).pathname;
if (0 != originalPath.indexOf(cookie.path || '/')) return next();
if (isCookieConfigurationSet) {
// pathname mismatch
var originalPath = parseUrl.original(req).pathname;
if (0 != originalPath.indexOf(cookie.path || '/')) return next();
}

// backwards compatibility for signed cookies
// req.secret is passed from the cookie parser middleware
Expand All @@ -163,8 +175,15 @@ function session(options){
// expose store
req.sessionStore = store;

// get the session ID from the cookie
var cookieId = req.sessionID = getcookie(req, name, secret);
var cookieId;
if (isCookieConfigurationSet) {
// get the session ID from the cookie
cookieId = req.sessionID = getcookie(req, name, secret);
}
if (headerName) {
// get the session ID from the header
cookieId = req.sessionID = getHeader(req, headerNameNormalized, secret);
}

// set-cookie
onHeaders(res, function(){
Expand All @@ -173,19 +192,22 @@ function session(options){
return;
}

var cookie = req.session.cookie;

// only send secure cookies via https
if (cookie.secure && !issecure(req, trustProxy)) {
debug('not secured');
return;
if (isCookieConfigurationSet) {
var cookie = req.session.cookie;
// only send secure cookies via https
if (cookie.secure && !issecure(req, trustProxy)) {
debug('not secured');
return;
}
if (!shouldSetCookie(req)) {
return;
}
setcookie(res, name, req.sessionID, secret, cookie.data);
}

if (!shouldSetCookie(req)) {
return;
if (headerName) {
setHeader(res, headerName, req.sessionID, secret);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

So setting a headerName will set the session id in header and cookie. Should this be exclusive?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This made to provide backward compatibility when part of front-end code still using cookie based authorization.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should the user put this in settings explicitly for having both? If I will use this functionality to have session id in headers, I will not want it in cookie no more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. You are right, user should have an ability to choose what type of session id passing he wants to use. I will try to update PR to support your suggestion.


setcookie(res, name, req.sessionID, secret, cookie.data);
});

// proxy end() to commit the session
Expand Down Expand Up @@ -573,3 +595,31 @@ function setcookie(res, name, val, secret, options) {

res.setHeader('set-cookie', header)
}

function setHeader(res, name, val, secret) {
var signed = 's:' + signature.sign(val, secret);
debug(name + ' %s', signed);

res.setHeader(name, signed);
}

function getHeader(req, name, secret) {
var header = req.headers[name];
var val;

// read from header
if (header) {
if (header.substr(0, 2) === 's:') {
val = signature.unsign(header.slice(2), secret);

if (val === false) {
debug('header signature invalid');
val = undefined;
}
} else {
debug('header unsigned')
}
}

return val;
}
4 changes: 3 additions & 1 deletion session/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ Session.prototype.touch = function(){
*/

Session.prototype.resetMaxAge = function(){
this.cookie.maxAge = this.cookie.originalMaxAge;
if (this.cookie) {
this.cookie.maxAge = this.cookie.originalMaxAge;
}
return this;
};

Expand Down
42 changes: 42 additions & 0 deletions test/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,38 @@ describe('session()', function(){
})
})

it('should load session from header sid', function (done) {
var count = 0
var header = 'X-Session-Token';
var server = createServer({ header: header }, function (req, res) {
req.session.num = req.session.num || ++count
res.end('session ' + req.session.num)
});

request(server)
.get('/')
.expect(shouldHaveHeader(header))
.expect(200, 'session 1', function (err, res) {
if (err) return done(err)
request(server)
.get('/')
.set(header, sidHeader(res, header))
.expect(200, 'session 1', done)
})
})

it('should not respond with cookie if configuration cookie key set as null', function (done) {
var header = 'X-Session-Token';
var server = createServer({ header: header, cookie: null }, function (req, res) {
res.end('session')
});

request(server)
.get('/')
.expect(shouldNotHaveHeader('Set-Cookie'))
.expect(200, 'session', done)
})

it('should pass session fetch error', function (done) {
var store = new session.MemoryStore()
var server = createServer({ store: store }, function (req, res) {
Expand Down Expand Up @@ -1940,6 +1972,12 @@ function shouldNotHaveHeader(header) {
}
}

function shouldHaveHeader(header) {
return function (res) {
assert.ok(header.toLowerCase() in res.headers, 'should have ' + header + ' header')
}
}

function shouldSetCookie(name) {
return function (res) {
var header = cookie(res)
Expand All @@ -1963,6 +2001,10 @@ function sid(res) {
return val
}

function sidHeader(res, name) {
return res.headers[name.toLowerCase()]
}

function writePatch() {
var ended = false
return function addWritePatch(req, res, next) {
Expand Down