client-sessions is connect middleware that implements sessions in encrypted tamper-free cookies. For a complete introduction to encrypted client side sessions, refer to Francois Marier's blog post on the subject;
NOTE: It is not recommended using both this middleware and connect's built-in session middleware.
npm install client-sessions
Basic usage:
varsessions=require("client-sessions");app.use(sessions({cookieName: 'mySession',// cookie name dictates the key name added to the request objectsecret: 'blargadeeblargblarg',// should be a large unguessable stringduration: 24*60*60*1000,// how long the session will stay valid in msactiveDuration: 1000*60*5// if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds}));app.use(function(req,res,next){if(req.mySession.seenyou){res.setHeader('X-Seen-You','true');}else{// setting a property will automatically cause a Set-Cookie response// to be sentreq.mySession.seenyou=true;res.setHeader('X-Seen-You','false');}});You can control more specific cookie behavior during setup:
app.use(sessions({cookieName: 'mySession',// cookie name dictates the key name added to the request objectsecret: 'blargadeeblargblarg',// should be a large unguessable stringduration: 24*60*60*1000,// how long the session will stay valid in mscookie: {path: '/api',// cookie will only be sent to requests under '/api'maxAge: 60000,// duration of the cookie in milliseconds, defaults to duration aboveephemeral: false,// when true, cookie expires when the browser closeshttpOnly: true,// when true, cookie is not accessible from javascriptsecure: false// when true, cookie will only be sent over SSL. use key 'secureProxy' instead if you handle SSL not in your node process}}));You can have multiple cookies:
// a 1 week sessionapp.use(sessions({cookieName: 'shopping_cart',secret: 'first secret',duration: 7*24*60*60*1000}));// a 2 hour encrypted sessionapp.use(sessions({cookieName: 'authenticated',secret: 'first secret',duration: 2*60*60*1000}));In this example, there's a 2 hour authentication session, but shopping carts persist for a week.
Finally, you can use requestKey to force the name where information can be accessed on the request object.
varsessions=require("client-sessions");app.use(sessions({cookieName: 'mySession',requestKey: 'forcedSessionKey',// requestKey overrides cookieName for the key name added to the request object.secret: 'blargadeeblargblarg',// should be a large unguessable string or Bufferduration: 24*60*60*1000,// how long the session will stay valid in ms}));app.use(function(req,res,next){// requestKey forces the session information to be// accessed via forcedSessionKeyif(req.forcedSessionKey.seenyou){res.setHeader('X-Seen-You','true');}next();});A pair of encryption and signature keys are derived from the secret option
via HMAC-SHA-256; the secret isn't used directly to encrypt or compute the
MAC.
The key-derivation function, in pseudocode:
encKey := HMAC-SHA-256(secret, 'cookiesession-encryption');
sigKey := HMAC-SHA-256(secret, 'cookiesession-signature');
The AES-256-CBC cipher is used to encrypt the session contents, with an HMAC-SHA-256 authentication tag (via Encrypt-then-Mac composition). A random 128-bit Initialization Vector (IV) is generated for each encryption operation (this is the AES block size regardless of the key size). The CBC-mode input is padded with the usual PKCS#5 scheme.
In pseudocode, the encryption looks like the following, with || denoting
concatenation. The createdAt and duration parameters are decimal strings.
sessionText := cookieName || '=' || sessionJson
iv := secureRandom(16 bytes)
ciphertext := AES-256-CBC(encKey, iv, sessionText)
payload := iv || '.' || ciphertext || '.' || createdAt || '.' || duration
hmac := HMAC-SHA-256(sigKey, payload)
cookie := base64url(iv) || '.' ||
base64url(ciphertext) || '.' ||
createdAt || '.' ||
duration || '.' ||
base64url(hmac)
For decryption, a constant-time equality operation is used to verify the HMAC output to avoid the plausible timing attack.
The defaults are secure, but may not suit your requirements. Some example scenarios:
- You want to use randomly-generated keys instead of using the key-derivation function used in this module.
- AES-256 is overkill for the type of data you store in the session (e.g. not personally-identifiable or sensitive) and you'd like to trade-off decreasing the security level for CPU economy.
- SHA-256 is maybe too weak for your application and you want to have more MAC security by using SHA-512, which grows the size of your cookies slightly.
If the defaults don't suit your needs, you can customize client-sessions. Beware: Changing keys and/or algorithms will make previously-generated Cookies invalid!
To configure independent encryption and signature (HMAC) keys:
app.use(sessions({encryptionKey: loadFromKeyStore('session-encryption-key'),signatureKey: loadFromKeyStore('session-signature-key'),// ... other options discussed above ...}));To specify custom algorithms and keys:
app.use(sessions({// use WEAKER-than-default encryption:encryptionAlgorithm: 'aes128',encryptionKey: loadFromKeyStore('session-encryption-key'),// use a SHORTER-than-default MAC:signatureAlgorithm: 'sha256-drop128',signatureKey: loadFromKeyStore('session-signature-key'),// ... other options discussed above ...}));Supported CBC-mode encryptionAlgorithms (and key length requirements):
| Cipher | Key length |
|---|---|
| aes128 | 16 bytes |
| aes192 | 24 bytes |
| aes256 | 32 bytes |
These key lengths are exactly as required by the Advanced Encryption Standard.
Supported HMAC signatureAlgorithms (and key length requirements):
| HMAC | Minimum Key Length | Maximum Key Length |
|---|---|---|
| sha256 | 32 bytes | 64 bytes |
| sha256-drop128 | 32 bytes | 64 bytes |
| sha384 | 48 bytes | 128 bytes |
| sha384-drop192 | 48 bytes | 128 bytes |
| sha512 | 64 bytes | 128 bytes |
| sha512-drop256 | 64 bytes | 128 bytes |
The HMAC key length requirements are derived from RFC 2104 section 3. The maximum key length can be exceeded, but it doesn't increase the security of the signature.
The -dropN algorithms discard the latter half of the HMAC output, which
provides some additional protection against SHA2 length-extension attacks on
top of HMAC. The same technique is used in the upcoming JSON Web Algorithms
AES_CBC_HMAC_SHA2 authenticated
cipher.
One can easily generate both AES and HMAC-SHA2 keys via command line: openssl rand -base64 32 for a 32-byte (256-bit) key. It's easy to then parse that
output into a Buffer:
functionloadKeyFromStore(name){vartext=myConfig.keys[name];returnBuffer.from(text,'base64');}If you specify encryptionKey or signatureKey, you must supply the other as
well.
The following constraints must be met or an Error will be thrown:
- both keys must be
Buffers. - the keys must be different.
- the encryption key are exactly the length required (see above).
- the signature key has at least the length required (see above).
Based on the above, please note that if you specify a secretand a
signatureAlgorithm, you need to use sha256 or sha256-drop128.
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
