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.
Installation
npm install client-sessions
Usage
Basic usage:
var sessions = require("client-sessions");
app.use(sessions({
cookieName: 'mySession',
secret: 'blargadeeblargblarg',
duration: 24 * 60 * 60 * 1000,
activeDuration: 1000 * 60 * 5
}));
app.use(function(req, res, next) {
if (req.mySession.seenyou) {
res.setHeader('X-Seen-You', 'true');
} else {
req.mySession.seenyou = true;
res.setHeader('X-Seen-You', 'false');
}
});
You can control more specific cookie behavior during setup:
app.use(sessions({
cookieName: 'mySession',
secret: 'blargadeeblargblarg',
duration: 24 * 60 * 60 * 1000,
cookie: {
path: '/api',
maxAge: 60000,
ephemeral: false,
httpOnly: true,
secure: false
}
}));
You can have multiple cookies:
app.use(sessions({
cookieName: 'shopping_cart',
secret: 'first secret',
duration: 7 * 24 * 60 * 60 * 1000
}));
app.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.
var sessions = require("client-sessions");
app.use(sessions({
cookieName: 'mySession',
requestKey: 'forcedSessionKey',
secret: 'blargadeeblargblarg',
duration: 24 * 60 * 60 * 1000,
}));
app.use(function(req, res, next) {
if (req.forcedSessionKey.seenyou) {
res.setHeader('X-Seen-You', 'true');
}
next();
});
Cryptography
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.
Advanced Cryptographic Options
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!
Configuring Keys
To configure independent encryption and signature (HMAC) keys:
app.use(sessions({
encryptionKey: loadFromKeyStore('session-encryption-key'),
signatureKey: loadFromKeyStore('session-signature-key'),
}));
Configuring Algorithms
To specify custom algorithms and keys:
app.use(sessions({
encryptionAlgorithm: 'aes128',
encryptionKey: loadFromKeyStore('session-encryption-key'),
signatureAlgorithm: 'sha256-drop128',
signatureKey: loadFromKeyStore('session-signature-key'),
}));
Encryption Algorithms
Supported CBC-mode encryptionAlgorithm
s (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.
Signature (HMAC) Algorithms
Supported HMAC signatureAlgorithm
s (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.
Generating Keys
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
:
function loadKeyFromStore(name) {
var text = myConfig.keys[name];
return Buffer.from(text, 'base64');
}
Key Constraints
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
Buffer
s. - 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 secret
and a
signatureAlgorithm
, you need to use sha256
or sha256-drop128
.
License
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/.