You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 7-8.RSVP
Socket
Socket
Sign inDemoInstall

express-jwt-blacklist

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.2 to 1.1.0

43

lib/index.js

@@ -10,3 +10,3 @@ 'use strict';

*
* - For type `revoked` we check if the token `iat` matches any of the `revoke` timestamps
* - For type `revoked` we check if the index claim, (default: `iat`) matches any of the `revoke` values
* - For type `purge` we check if `iat` is older then the timestamp the `purge` timestamp

@@ -29,2 +29,3 @@ *

var tokenId = 'sub';
var indexBy = 'iat';
var keyPrefix = 'jwt-blacklist:';

@@ -76,2 +77,6 @@ var strict = false;

}
if (opts.indexBy) {
utils.checkString(opts.indexBy, 'indexBy');
indexBy = opts.indexBy;
}
if (opts.strict) {

@@ -96,2 +101,3 @@ utils.checkBoolean(opts.strict, 'strict');

* @param {Object} user JWT user payload
* @param {Number} Optional lifetime (in seconds) for this entry
* @param {Function} [fn] Optional callback function

@@ -105,2 +111,3 @@ */

* @param {Object} user JWT user payload
* @param {Number} Optional lifetime (in seconds) for this entry
* @param {Function} [fn] Optional callback function

@@ -121,3 +128,5 @@ */

var id = user[tokenId];
if (!id) return fn(new Error('JWT missing tokenId ' + tokenId));
if (!id) return fn(new Error('JWT missing tokenId claim' + tokenId));
var index = user[indexBy];
if (!index) return fn(new Error('JWT missing indexBy claim' + tokenId));

@@ -130,3 +139,3 @@ var key = keyPrefix + id;

if (res[TYPE.revoke] && res[TYPE.revoke].indexOf(user.iat) !== -1) revoked = true;
if (res[TYPE.revoke] && res[TYPE.revoke].indexOf(index) !== -1) revoked = true;
else if (res[TYPE.purge] >= user.iat) revoked = true;

@@ -139,9 +148,15 @@ else revoked = false;

function operation(type, user, fn) {
function operation(type, user, lifetime, fn) {
if (typeof fn !== 'function') fn = utils.optionalCallback;
if (typeof lifetime === 'function') {
fn = lifetime;
lifetime = undefined;
} else if (lifetime && typeof lifetime !== 'number') {
return fn(new Error('Invalid lifetime value'));
}
if (!user) return fn(new Error('User payload missing'));
if (typeof user.iat !== 'number') return fn(new Error('Invalid user.iat value'));
if (typeof fn !== 'function') fn = utils.optionalCallback;
if (!lifetime && typeof user.iat !== 'number') return fn(new Error('Invalid user.iat value'));
var id = user[tokenId];
if (!id) return fn(new Error('JWT missing tokenId ' + tokenId));
if (!id) return fn(new Error('JWT missing tokenId claim' + tokenId));

@@ -153,11 +168,13 @@ var key = keyPrefix + id;

var data = res || {};
debug('revoke [' + key + '] ' + user.iat, data);
debug('revoke [' + key + '] ' + index, data);
if (type === TYPE.revoke) {
var index = user[indexBy];
if (!index) return fn(new Error('JWT missing indexBy claim' + tokenId));
if (data[TYPE.revoke]) {
if (data[TYPE.revoke].indexOf(user.iat) === -1) {
data[TYPE.revoke].push(user.iat);
if (data[TYPE.revoke].indexOf(index) === -1) {
data[TYPE.revoke].push(index);
}
}
else data[TYPE.revoke] = [user.iat];
else data[TYPE.revoke] = [index];
}

@@ -169,5 +186,5 @@

var lifetime = user.exp ? user.exp - user.iat : 0;
lifetime = lifetime ? lifetime : (user.exp ? user.exp - user.iat : 0);
store.set(key, data, lifetime, fn);
});
};

@@ -15,3 +15,3 @@ 'use strict';

var memcached = new Memcached(host + ':' + port, store.options || {});
var memcached = store.client || new Memcached(host + ':' + port, store.options || {});
memcached.on('issue', issue);

@@ -18,0 +18,0 @@ memcached.on('failure', failure);

@@ -16,3 +16,3 @@ 'use strict';

var client = redis.createClient(port, host, store.options || {});
var client = store.client || redis.createClient(port, host, store.options || {});
client.on('error', error);

@@ -22,2 +22,6 @@

set: function(key, value, lifetime, fn) {
// Serialize array
if (value[blacklist.TYPE.revoke]) {
value[blacklist.TYPE.revoke] = value[blacklist.TYPE.revoke].toString();
}
client.hmset(key, value, fn);

@@ -28,6 +32,6 @@ if (lifetime) client.expire(key, lifetime);

client.hgetall(key, function(err, res) {
// De-serialize comma separated value to iat numbers
// De-serialize comma separated value, convert to numbers if necessary
if (res && res[blacklist.TYPE.revoke]) {
res[blacklist.TYPE.revoke] = res[blacklist.TYPE.revoke].split(',').map(function(i) {
return parseInt(i, 10);
return (isNaN(i)) ? i : parseInt(i, 10);
});

@@ -34,0 +38,0 @@ }

@@ -18,3 +18,3 @@ 'use strict';

exports.nowInSeconds = function() {
return Math.round(new Date().getTime() / 1000);
return Math.floor(new Date().getTime() / 1000);
}
{
"name": "express-jwt-blacklist",
"version": "1.0.2",
"version": "1.1.0",
"description": "express-jwt plugin for token blacklisting",

@@ -23,3 +23,3 @@ "main": "lib/index.js",

"memcached": "^2.2.1",
"redis": "^2.4.2"
"redis": "^2.5.3"
},

@@ -26,0 +26,0 @@ "devDependencies": {

@@ -44,2 +44,3 @@ # express-jwt-blacklist

- `store.type` - Store type `memory`, `memcached` or `redis` (default: `memory`)
- `store.client` - Client object, obviates store.host, store.port, store.options
- `store.host` - Store host (default: `127.0.0.1`)

@@ -49,3 +50,4 @@ - `store.port` - Store port (default: `11211` memcached, `6379` redis)

- `store.options` - Additional store client options (default: `{}`)
- `tokenId` - Unique JWT token identifier (default: `sub`)
- `tokenId` - JWT claim unique to user (default: `sub`)
- `indexBy` - JWT claim used for revocation (default: `iat`), note that purge still uses `iat`
- `strict` - Strict revocation policy will return revoked `true` on store failure (default: `false`)

@@ -73,10 +75,20 @@

### blacklist.revoke(user)
### blacklist.revoke(user, [optionalLifetime], [optionalCallbackFn])
This function will revoke a token, by passing in the `req.user` set by express-jwt library.
This function will revoke a token, by passing in a token payload skeleton in the `req.user` format set by the express-jwt library. The lifetime of the revocation entry in the store, can optionally be set explicitly (in seconds), and is otherwise calculated from the `exp` claim. If no argument is provided and the token is missing the `exp` claim, the revocation entry will not expire. An optional callback function can be supplied that will be called on error with the error as its only argument.
### blacklist.purge(user)
Typically, the server backend will call this function when a particular route is hit and the token to be revoked is the same one supplied for authentication, i.e. in a logout route initiated by the user in question. Alternatively, the backend can construct a token payload skeleton, which may be useful in a case where an admin user would like to forcibly logout a user from a single session. In the latter case, it may be useful to set the lifetime argument explicitly, as the proper value for the `exp` claim will likely be unavailable.
This function will purge **all** tokens older than current timestamp, by passing in the `req.user` set by express-jwt library.
By default, revocation is based on the claim specified by `tokenId` as well as the `iat` claim, resulting in revocation of only the provided `req.user` token. The optional `index` configuration argument allows revocation of all tokens issued for a specific user that share the same value for the specified claim with `req.user`.
The `index` argument may be useful if tokens are being refreshed, and you would therefore like to invalidate some, but not all, of the previously issued tokens, e.g. only those from a specific session.
In particular, your token scheme may use the `sub` claim to represent the user, and the `jti` claim to represent a session, where the original and all subsequent refreshed tokens contain identical `sub` and `jti` claims, but other sessions for the user contain an identical `sub` claim, but different `jti` claims. In this scenario, `tokenId` would be set to `sub` (the default), and the `index` should be set to `jti`. Note that if one user in this scenario is issued a token with a `jti` claim identical to a token that has been revoked for a different user, it will still not be marked as revoked, as revocation is always based on the `tokenId` as well as the `index` argument.
### blacklist.purge(user, [optionalLifetime], [optionalCallbackFn])
This function will purge **all** tokens older than current timestamp, by passing in a a token payload skeleton in the `req.user` format set by the express-jwt library. The lifetime of the revocation entry in the store, can optionally be set explicitly (in seconds), and is otherwise calculated from the `exp` claim. If no argument is provided and the token is missing the `exp` claim, the revocation entry will not expire. An optional callback function can be supplied that will be called on error with the error as its only argument.
Typically, the server backend will call this function when a particular route is hit and the tokens to be purged are similar to the one supplied for authentication, i.e. in a password change route initiated by the user in question. Alternatively, the backend can construct a token payload skeleton, which may be useful in a case where an admin user would like to forcibly logout all sessions for a different user. In the latter case, it may be useful to set the lifetime argument explicitly, as the proper value for the `exp` claim will likely be unavailable.
### Custom store

@@ -89,9 +101,9 @@

### Considerations
### Token Payload Considerations
User object `req.user` that's being set by the express-jwt library **should** contain and match `tokenId` from configuration.
User object `req.user` that's being set by the express-jwt library **should** contain claims matching `tokenId` and 'indexBy' from configuration.
- You need to set either `sub` or `jti` or some other key in the payload when siging a JWT token.
- Issued at `iat` timestamp should be present.
- Expiration timestamp `exp` is optional but desired.
- At a minimum, you need to set either `sub` or `jti` or some other claim in the payload when signing a JWT token to identify a user.
- Expiration timestamp `exp` claim is optional but desired, as it will allow for expiration of revocation entries from the store, increasing the speed of the `isRevoked` check. Alternatively, a specified lifetime value can be passed to each revoke/purge call by the backend.
- Issued at `iat` timestamp claim must be present, even if `indexBy` is set to another claim, so as to allow purge operations to work. `iat` is also used to calculate token lifetime, if no specified lifetime is set, and `exp` is present.

@@ -98,0 +110,0 @@ ## Why blacklist?

@@ -1,2 +0,2 @@

/*globals describe it*/
/*globals describe, it, before*/
'use strict';

@@ -7,8 +7,2 @@

var blacklist = require('../../lib');
blacklist.configure({
store: {
type: 'memcached',
keyPrefix: 'express-jwt-blacklist-test:'
}
});

@@ -22,2 +16,12 @@ var JWT_USER = {

describe('Blacklist memcached operations', function() {
before(function(done) {
blacklist.configure({
store: {
type: 'memcached',
keyPrefix: 'express-jwt-blacklist-test:'
}
});
done();
});
it('isRevoked should return false', function(done) {

@@ -24,0 +28,0 @@ blacklist.isRevoked({}, JWT_USER, function(err, revoked) {

@@ -1,2 +0,2 @@

/*globals describe it*/
/*globals describe, it, before*/
'use strict';

@@ -7,8 +7,2 @@

var blacklist = require('../../lib');
blacklist.configure({
store: {
type: 'redis',
keyPrefix: 'express-jwt-blacklist-test:'
}
});

@@ -22,2 +16,12 @@ var JWT_USER = {

describe('Blacklist redis operations', function() {
before(function(done) {
blacklist.configure({
store: {
type: 'redis',
keyPrefix: 'express-jwt-blacklist-test:'
}
});
done();
});
it('isRevoked should return false', function(done) {

@@ -24,0 +28,0 @@ blacklist.isRevoked({}, JWT_USER, function(err, revoked) {

@@ -1,2 +0,2 @@

/*globals describe it*/
/*globals describe, it*/
'use strict';

@@ -52,2 +52,12 @@

});
it('should throw error on invalid indexBy configuration', function() {
try {
blacklist.configure({
indexBy: 123
});
} catch(e) {
should.exist(e);
}
});

@@ -116,2 +126,22 @@ it('should throw error on invalid keyPrefix configuration', function() {

});
it('revoke should revoke another JWT token without a callback', function(done) {
JWT_USER.iat += 10;
blacklist.revoke(JWT_USER)
blacklist.isRevoked({}, JWT_USER, function(err, revoked) {
should.not.exist(err);
revoked.should.be.true();
done();
});
});
it('revoke should revoke another JWT token without the full original token', function(done) {
JWT_USER.iat += 10;
blacklist.revoke({ iat: JWT_USER.iat, sub: JWT_USER.sub })
blacklist.isRevoked({}, JWT_USER, function(err, revoked) {
should.not.exist(err);
revoked.should.be.true();
done();
});
});
});

@@ -1,2 +0,2 @@

/*globals describe it*/
/*globals describe, it, before*/
'use strict';

@@ -17,3 +17,3 @@

describe('Blacklist custom store', function() {
beforeEach(function() {
before(function() {
blacklist.configure({

@@ -20,0 +20,0 @@ store: {

@@ -1,2 +0,2 @@

/*globals describe it*/
/*globals describe, it*/
'use strict';

@@ -3,0 +3,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc