Skip to content

Commit 49d54e5

Browse files
committed
add verify option maxAge (with tests)
1 parent d7c5793 commit 49d54e5

File tree

2 files changed

+87
-11
lines changed

2 files changed

+87
-11
lines changed

index.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var JWT = module.exports;
44

55
var JsonWebTokenError = JWT.JsonWebTokenError = require('./lib/JsonWebTokenError');
66
var TokenExpiredError = JWT.TokenExpiredError = require('./lib/TokenExpiredError');
7-
7+
var ms = require('ms')
88

99
JWT.decode = function (jwt, options) {
1010
options = options || {};
@@ -195,5 +195,15 @@ JWT.verify = function(jwtString, secretOrPublicKey, options, callback) {
195195
return done(new JsonWebTokenError('jwt issuer invalid. expected: ' + options.issuer));
196196
}
197197

198+
if (options.maxAge) {
199+
var maxAge = ms(options.maxAge);
200+
if (typeof payload.iat !== 'number') {
201+
return done(new JsonWebTokenError('iat required when maxAge is specified'));
202+
}
203+
if (Date.now() - (payload.iat * 1000) > maxAge) {
204+
return done(new TokenExpiredError('maxAge exceded', new Date(payload.iat * 1000 + maxAge)));
205+
}
206+
}
207+
198208
return done(null, payload);
199209
};

test/verify.tests.js

+76-10
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,20 @@ describe('verify', function() {
2929
});
3030

3131
describe('expiration', function () {
32+
// { foo: 'bar', iat: 1437018582, exp: 1437018583 }
33+
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU4M30.NmMv7sXjM1dW0eALNXud8LoXknZ0mH14GtnFclwJv0s';
34+
var key = 'key';
35+
3236
var clock;
33-
beforeEach(function () {
34-
// clock = sinon.useFakeTimers(1437018650768);
35-
});
3637
afterEach(function () {
3738
try { clock.restore(); } catch (e) {}
3839
});
3940

4041
it('should error on expired token', function (done) {
41-
clock = sinon.useFakeTimers(1437018650768);
42-
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU4M30.NmMv7sXjM1dW0eALNXud8LoXknZ0mH14GtnFclwJv0s';
43-
var key = 'key';
44-
jwt.verify(token, key, {algorithms: ['HS256']}, function (err, p) {
42+
clock = sinon.useFakeTimers(1437018650000);
43+
var options = {algorithms: ['HS256']};
44+
45+
jwt.verify(token, key, options, function (err, p) {
4546
assert.equal(err.name, 'TokenExpiredError');
4647
assert.equal(err.message, 'jwt expired');
4748
assert.equal(err.expiredAt.constructor.name, 'Date');
@@ -53,14 +54,79 @@ describe('verify', function() {
5354

5455
it('should not error on unexpired token', function (done) {
5556
clock = sinon.useFakeTimers(1437018582000);
56-
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE0MzcwMTg1ODIsImV4cCI6MTQzNzAxODU4M30.NmMv7sXjM1dW0eALNXud8LoXknZ0mH14GtnFclwJv0s';
57-
var key = 'key';
58-
jwt.verify(token, key, {algorithms: ['HS256']}, function (err, p) {
57+
var options = {algorithms: ['HS256']}
58+
59+
jwt.verify(token, key, options, function (err, p) {
5960
assert.isNull(err);
6061
assert.equal(p.foo, 'bar');
6162
done();
6263
});
6364
});
65+
66+
describe('option: maxAge', function () {
67+
it('should error for claims issued before a certain timespan', function (done) {
68+
clock = sinon.useFakeTimers(1437018582500);
69+
var options = {algorithms: ['HS256'], maxAge: '321ms'};
70+
71+
jwt.verify(token, key, options, function (err, p) {
72+
assert.equal(err.name, 'TokenExpiredError');
73+
assert.equal(err.message, 'maxAge exceded');
74+
assert.equal(err.expiredAt.constructor.name, 'Date');
75+
assert.equal(Number(err.expiredAt), 1437018582321);
76+
assert.isUndefined(p);
77+
done();
78+
});
79+
});
80+
it('should not error if within maxAge timespan', function (done) {
81+
clock = sinon.useFakeTimers(1437018582500);
82+
var options = {algorithms: ['HS256'], maxAge: '600ms'};
83+
84+
jwt.verify(token, key, options, function (err, p) {
85+
assert.isNull(err);
86+
assert.equal(p.foo, 'bar');
87+
done();
88+
});
89+
});
90+
it('can be more restrictive than expiration', function (done) {
91+
clock = sinon.useFakeTimers(1437018582900);
92+
var options = {algorithms: ['HS256'], maxAge: '800ms'};
93+
94+
jwt.verify(token, key, options, function (err, p) {
95+
assert.equal(err.name, 'TokenExpiredError');
96+
assert.equal(err.message, 'maxAge exceded');
97+
assert.equal(err.expiredAt.constructor.name, 'Date');
98+
assert.equal(Number(err.expiredAt), 1437018582800);
99+
assert.isUndefined(p);
100+
done();
101+
});
102+
});
103+
it('cannot be more permissive than expiration', function (done) {
104+
clock = sinon.useFakeTimers(1437018583100);
105+
var options = {algorithms: ['HS256'], maxAge: '1200ms'};
106+
107+
jwt.verify(token, key, options, function (err, p) {
108+
// maxAge not exceded, but still expired
109+
assert.equal(err.name, 'TokenExpiredError');
110+
assert.equal(err.message, 'jwt expired');
111+
assert.equal(err.expiredAt.constructor.name, 'Date');
112+
assert.equal(Number(err.expiredAt), 1437018583000);
113+
assert.isUndefined(p);
114+
done();
115+
});
116+
});
117+
it('should error if maxAge is specified but there is no iat claim', function (done) {
118+
clock = sinon.useFakeTimers(1437018582900);
119+
var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.0MBPd4Bru9-fK_HY3xmuDAc6N_embknmNuhdb9bKL_U';
120+
var options = {algorithms: ['HS256'], maxAge: '1s'};
121+
122+
jwt.verify(token, key, options, function (err, p) {
123+
assert.equal(err.name, 'JsonWebTokenError');
124+
assert.equal(err.message, 'iat required when maxAge is specified');
125+
assert.isUndefined(p);
126+
done();
127+
});
128+
});
129+
});
64130
});
65131

66132
});

0 commit comments

Comments
 (0)