express-openid-connect
Advanced tools
Comparing version 2.10.0 to 2.11.0
@@ -249,5 +249,8 @@ // Type definitions for express-openid-connect | ||
/** | ||
* REQUIRED. The secret(s) used to derive an encryption key for the user identity in a session cookie and | ||
* to sign the transient cookies used by the login callback. | ||
* Use a single string key or array of keys for an encrypted session cookie. | ||
* REQUIRED. The secret(s) used to derive an encryption key for the user identity in a stateless session cookie, | ||
* to sign the transient cookies used by the login callback and to sign the custom session store cookies if | ||
* {@Link signSessionStoreCookie} is `true`. Use a single string key or array of keys. | ||
* If an array of secrets is provided, only the first element will be used to sign or encrypt the values, while all | ||
* the elements will be considered when decrypting or verifying the values. | ||
* | ||
* Can use env key SECRET instead. | ||
@@ -641,5 +644,7 @@ */ | ||
* | ||
* **IMPORTANT** If you override this method you must use a suitable | ||
* cryptographically strong random value of sufficient size to prevent collisions | ||
* and reduce the ability to hijack a session by guessing the session ID. | ||
* **IMPORTANT** If you override this method you should be careful to generate | ||
* unique IDs so your sessions do not conflict. Also, to reduce the ability | ||
* to hijack a session by guessing the session ID, you must use a suitable | ||
* cryptographically strong random value of sufficient size or sign the cookie | ||
* by setting {@Link signSessionStoreCookie} to `true`. | ||
*/ | ||
@@ -649,2 +654,26 @@ genid?: (req: OpenidRequest) => string; | ||
/** | ||
* Sign the session store cookies to reduce the chance of collisions | ||
* and reduce the ability to hijack a session by guessing the session ID. | ||
* | ||
* This is required if you override {@Link genid} and don't use a suitable | ||
* cryptographically strong random value of sufficient size. | ||
*/ | ||
signSessionStoreCookie: boolean; | ||
/** | ||
* If you enable {@Link signSessionStoreCookie} your existing sessions will | ||
* be invalidated. You can use this flag to temporarily allow unsigned cookies | ||
* while you sign your user's session cookies. For example: | ||
* | ||
* Set {@Link signSessionStoreCookie} to `true` and {@Link requireSignedSessionStoreCookie} to `false`. | ||
* Wait for your {@Link rollingDuration} (default 1 day) or {@Link absoluteDuration} (default 1 week) | ||
* to pass (which ever comes first). By this time all your sessions cookies will either be signed or | ||
* have expired, then you can remove the {@Link requireSignedSessionStoreCookie} config option which | ||
* will set it to `true`. | ||
* | ||
* Signed session store cookies will be mandatory in the next major release. | ||
*/ | ||
requireSignedSessionStoreCookie: boolean; | ||
/** | ||
* If you want your session duration to be rolling, eg reset everytime the | ||
@@ -651,0 +680,0 @@ * user is active on your site, set this to a `true`. If you want the session |
const { strict: assert, AssertionError } = require('assert'); | ||
const { | ||
JWK, | ||
JWKS, | ||
JWE, | ||
@@ -12,3 +10,3 @@ errors: { JOSEError }, | ||
const COOKIES = require('./cookies'); | ||
const { encryption: deriveKey } = require('./hkdf'); | ||
const { getKeyStore, verifyCookie, signCookie } = require('./crypto'); | ||
const debug = require('./debug')('appSession'); | ||
@@ -51,9 +49,4 @@ | ||
module.exports = (config) => { | ||
let current; | ||
const alg = 'dir'; | ||
const enc = 'A256GCM'; | ||
const secrets = Array.isArray(config.secret) | ||
? config.secret | ||
: [config.secret]; | ||
const sessionName = config.session.name; | ||
@@ -66,2 +59,4 @@ const cookieConfig = config.session.cookie; | ||
rollingDuration, | ||
signSessionStoreCookie, | ||
requireSignedSessionStoreCookie, | ||
} = config.session; | ||
@@ -80,12 +75,3 @@ | ||
let keystore = new JWKS.KeyStore(); | ||
secrets.forEach((secretString, i) => { | ||
const key = JWK.asKey(deriveKey(secretString)); | ||
if (i === 0) { | ||
current = key; | ||
} | ||
keystore.add(key); | ||
}); | ||
let [current, keystore] = getKeyStore(config.secret, true); | ||
if (keystore.size === 1) { | ||
@@ -197,2 +183,6 @@ keystore = current; | ||
getCookie(req) { | ||
return req[COOKIES][sessionName]; | ||
} | ||
setCookie(req, res, iat) { | ||
@@ -208,2 +198,9 @@ setCookie(req, res, iat); | ||
this._destroy = promisify(store.destroy).bind(store); | ||
let [current, keystore] = getKeyStore(config.secret); | ||
if (keystore.size === 1) { | ||
keystore = current; | ||
} | ||
this._keyStore = keystore; | ||
this._current = current; | ||
} | ||
@@ -240,2 +237,17 @@ | ||
getCookie(req) { | ||
if (signSessionStoreCookie) { | ||
const verified = verifyCookie( | ||
sessionName, | ||
req[COOKIES][sessionName], | ||
this._keyStore | ||
); | ||
if (requireSignedSessionStoreCookie) { | ||
return verified; | ||
} | ||
return verified || req[COOKIES][sessionName]; | ||
} | ||
return req[COOKIES][sessionName]; | ||
} | ||
setCookie( | ||
@@ -257,3 +269,7 @@ id, | ||
delete cookieOptions.transient; | ||
res.cookie(sessionName, id, cookieOptions); | ||
let value = id; | ||
if (signSessionStoreCookie) { | ||
value = signCookie(sessionName, id, this._current); | ||
} | ||
res.cookie(sessionName, value, cookieOptions); | ||
} | ||
@@ -292,3 +308,3 @@ } | ||
debug('reading session from %s cookie', sessionName); | ||
existingSessionValue = req[COOKIES][sessionName]; | ||
existingSessionValue = store.getCookie(req); | ||
} else if (req[COOKIES].hasOwnProperty(`${sessionName}.0`)) { | ||
@@ -295,0 +311,0 @@ // get JWE from chunked session cookie |
@@ -51,2 +51,6 @@ const Joi = require('joi'); | ||
.default(() => defaultSessionIdGenerator), | ||
signSessionStoreCookie: Joi.boolean().optional().default(false), | ||
requireSignedSessionStoreCookie: Joi.boolean() | ||
.optional() | ||
.default(Joi.ref('signSessionStoreCookie')), | ||
cookie: Joi.object({ | ||
@@ -53,0 +57,0 @@ domain: Joi.string().optional(), |
@@ -1,2 +0,1 @@ | ||
const cb = require('cb'); | ||
const url = require('url'); | ||
@@ -9,2 +8,3 @@ const urlJoin = require('url-join'); | ||
const debug = require('./debug')('context'); | ||
const { once } = require('./once'); | ||
const { get: getClient } = require('./client'); | ||
@@ -177,3 +177,3 @@ const { encodeState } = require('../lib/hooks/getLoginState'); | ||
let { config, req, res, next, transient } = weakRef(this); | ||
next = cb(next).once(); | ||
next = once(next); | ||
try { | ||
@@ -275,3 +275,3 @@ const client = await getClient(config); | ||
let { config, req, res, next } = weakRef(this); | ||
next = cb(next).once(); | ||
next = once(next); | ||
let returnURL = params.returnTo || config.routes.postLogoutRedirect; | ||
@@ -278,0 +278,0 @@ debug('req.oidc.logout() with return url: %s', returnURL); |
const { generators } = require('openid-client'); | ||
const { JWKS, JWS, JWK } = require('jose'); | ||
const { signing: deriveKey } = require('./hkdf'); | ||
const header = { alg: 'HS256', b64: false, crit: ['b64'] }; | ||
const getPayload = (cookie, value) => Buffer.from(`${cookie}=${value}`); | ||
const flattenedJWSFromCookie = (cookie, value, signature) => ({ | ||
protected: Buffer.from(JSON.stringify(header)) | ||
.toString('base64') | ||
.replace(/=/g, '') | ||
.replace(/\+/g, '-') | ||
.replace(/\//g, '_'), | ||
payload: getPayload(cookie, value), | ||
signature, | ||
}); | ||
const generateSignature = (cookie, value, key) => { | ||
const payload = getPayload(cookie, value); | ||
return JWS.sign.flattened(payload, key, header).signature; | ||
}; | ||
const verifySignature = (cookie, value, signature, keystore) => { | ||
try { | ||
return !!JWS.verify( | ||
flattenedJWSFromCookie(cookie, value, signature), | ||
keystore, | ||
{ algorithm: 'HS256', crit: ['b64'] } | ||
); | ||
} catch (err) { | ||
return false; | ||
} | ||
}; | ||
const getCookieValue = (cookie, value, keystore) => { | ||
if (!value) { | ||
return undefined; | ||
} | ||
let signature; | ||
[value, signature] = value.split('.'); | ||
if (verifySignature(cookie, value, signature, keystore)) { | ||
return value; | ||
} | ||
return undefined; | ||
}; | ||
const generateCookieValue = (cookie, value, key) => { | ||
const signature = generateSignature(cookie, value, key); | ||
return `${value}.${signature}`; | ||
}; | ||
const { | ||
signCookie: generateCookieValue, | ||
verifyCookie: getCookieValue, | ||
getKeyStore, | ||
} = require('./crypto'); | ||
const COOKIES = require('./cookies'); | ||
@@ -53,14 +11,4 @@ | ||
constructor({ secret, session, legacySameSiteCookie }) { | ||
let current; | ||
let [current, keystore] = getKeyStore(secret); | ||
const secrets = Array.isArray(secret) ? secret : [secret]; | ||
let keystore = new JWKS.KeyStore(); | ||
secrets.forEach((secretString, i) => { | ||
const key = JWK.asKey(deriveKey(secretString)); | ||
if (i === 0) { | ||
current = key; | ||
} | ||
keystore.add(key); | ||
}); | ||
if (keystore.size === 1) { | ||
@@ -67,0 +15,0 @@ keystore = current; |
const express = require('express'); | ||
const cb = require('cb'); | ||
const createError = require('http-errors'); | ||
const debug = require('../lib/debug')('auth'); | ||
const { once } = require('../lib/once'); | ||
const { get: getConfig } = require('../lib/config'); | ||
@@ -72,3 +72,3 @@ const { get: getClient } = require('../lib/client'); | ||
async (req, res, next) => { | ||
next = cb(next).once(); | ||
next = once(next); | ||
@@ -75,0 +75,0 @@ client = |
{ | ||
"name": "express-openid-connect", | ||
"version": "2.10.0", | ||
"version": "2.11.0", | ||
"description": "Express middleware to protect web applications using OpenID Connect.", | ||
@@ -29,3 +29,2 @@ "homepage": "https://github.com/auth0/express-openid-connect", | ||
"base64url": "^3.0.1", | ||
"cb": "^0.1.0", | ||
"clone": "^2.1.2", | ||
@@ -36,3 +35,3 @@ "cookie": "^0.5.0", | ||
"http-errors": "^1.8.1", | ||
"joi": "^17.6.3", | ||
"joi": "^17.7.0", | ||
"jose": "^2.0.6", | ||
@@ -39,0 +38,0 @@ "on-headers": "^1.0.2", |
91533
13
20
2549
- Removedcb@^0.1.0
- Removedcb@0.1.1(transitive)
Updatedjoi@^17.7.0