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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,54 @@ app.use(cookieSession({
// ... your logic here ...
```

### Custom encode/decode (encryption example)

If you want to encrypt (or compress) the session payload before it is
stored in the cookie you can provide `encode` and `decode` functions to the
middleware. Below is a minimal example using Node's `crypto` and AES-256-GCM.

Note: this is a compact example for demonstration. In production you should
securely manage keys, use unique nonces/IVs per-encryption, and consider
authenticity/rotation policies.

```js
var cookieSession = require('cookie-session')
var crypto = require('crypto')

// secretKey should be 32 bytes for AES-256
var secretKey = Buffer.from(process.env.SESSION_ENC_KEY, 'hex')

function encodeEncrypted (obj) {
var plaintext = JSON.stringify(obj)
var iv = crypto.randomBytes(12) // recommended nonce size for GCM
var cipher = crypto.createCipheriv('aes-256-gcm', secretKey, iv)
var encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
var tag = cipher.getAuthTag()

// store iv + tag + cipher in base64 for cookie transport
return Buffer.concat([iv, tag, encrypted]).toString('base64')
}

function decodeEncrypted (str) {
var buf = Buffer.from(str, 'base64')
var iv = buf.slice(0, 12)
var tag = buf.slice(12, 28)
var encrypted = buf.slice(28)

var decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, iv)
decipher.setAuthTag(tag)
var decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()])
return JSON.parse(decrypted.toString('utf8'))
}

app.use(cookieSession({
name: 'session',
keys: ['signing-key'], // still use signing for tamper protection
encode: encodeEncrypted,
decode: decodeEncrypted
}))
```

## Usage Limitations

### Max Cookie Size
Expand Down
23 changes: 17 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,18 @@ function cookieSession (options) {

debug('session options %j', opts)

// encode/decode overrides (allow encryption/compression/custom serialization)
var encodeFn = typeof opts.encode === 'function' ? opts.encode : encode
var decodeFn = typeof opts.decode === 'function' ? opts.decode : decode

return function _cookieSession (req, res, next) {
var cookies = new Cookies(req, res, {
keys: keys
})
var sess

// for overriding
req.sessionOptions = Object.create(opts)
// for overriding
req.sessionOptions = Object.create(opts)

// define req.session getter / setter
Object.defineProperty(req, 'session', {
Expand All @@ -86,7 +90,7 @@ function cookieSession (options) {
}

// get session
if ((sess = tryGetSession(cookies, name, req.sessionOptions))) {
if ((sess = tryGetSession(cookies, name, req.sessionOptions, decodeFn))) {
return sess
}

Expand Down Expand Up @@ -125,7 +129,7 @@ function cookieSession (options) {
} else if ((!sess.isNew || sess.isPopulated) && sess.isChanged) {
// save populated or non-new changed session
debug('save %s', name)
cookies.set(name, Session.serialize(sess), req.sessionOptions)
cookies.set(name, encodeFn(sess), req.sessionOptions)
}
} catch (e) {
debug('error saving session %s', e.message)
Expand Down Expand Up @@ -273,7 +277,7 @@ function encode (body) {
* @private
*/

function tryGetSession (cookies, name, opts) {
function tryGetSession (cookies, name, opts, decodeFn) {
var str = cookies.get(name, opts)

if (!str) {
Expand All @@ -283,7 +287,14 @@ function tryGetSession (cookies, name, opts) {
debug('parse %s', str)

try {
return Session.deserialize(str)
var obj = decodeFn(str)

// build session with context like Session.deserialize would do
var ctx = new SessionContext()
ctx._new = false
ctx._val = str

return new Session(ctx, obj)
} catch (err) {
return undefined
}
Expand Down
40 changes: 40 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,46 @@ describe('Cookie Session', function () {
})
})
})

describe('custom encode/decode', function () {
it('should allow overriding encode and decode functions', function (done) {
// simple encode/decode that prefixes with 'X:' and base64 encodes JSON
function myEncode (obj) {
var str = JSON.stringify(obj)
return 'X:' + Buffer.from(str).toString('base64')
}

function myDecode (str) {
if (str.indexOf('X:') !== 0) throw new Error('bad prefix')
var body = Buffer.from(str.slice(2), 'base64').toString('utf8')
return JSON.parse(body)
}

var app = connect()
app.use(session({ keys: ['a'], encode: myEncode, decode: myDecode }))
app.use(function (req, res) {
if (req.url === '/set') {
req.session.message = 'hello-enc'
return res.end()
}

res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(req.session))
})

request(app)
.get('/set')
.expect(shouldHaveCookie('session'))
.expect(200, '', function (err, res) {
if (err) return done(err)

request(app)
.get('/')
.set('Cookie', cookieHeader(cookies(res)))
.expect(200, { message: 'hello-enc' }, done)
})
})
})
})

function App (options) {
Expand Down